@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 CHANGED
@@ -1,18 +1,16 @@
1
1
  # Contributing
2
2
 
3
3
  Starting a new session? Read the [maintainer handoff](docs/maintainer-handoff.md)
4
- and [repository instructions](https://github.com/ciqol/resenv/blob/beta/AGENTS.md) before editing or releasing.
4
+ and [repository instructions](https://github.com/ciqol/resenv/blob/main/AGENTS.md) before editing or releasing.
5
5
 
6
6
  The [README](README.md) covers using resenv in an application. This guide covers
7
7
  changing resenv itself and verifying those changes.
8
8
 
9
- Releases start at `0.1.0-beta.1` on the sole branch `beta`, using npm channel
10
- `beta`. Semantic-release's official commit-analysis and release-notes plugins
11
- select releasable changes and generate notes; actual beta tags supply the next
12
- prerelease counter. The root and ordinary local builds remain private prototypes.
13
- CI generates the publishable manifest before packing and testing it, then stages
14
- the exact archive through npm OIDC for maintainer approval. See
15
- [releasing](docs/releasing.md) and the bounded [release-tool review](docs/release-security.md).
9
+ Merge to protected `main` approves release. CI uses standard semantic-release
10
+ and the official npm/GitHub plugins to publish regular `0.x.x` versions to
11
+ `latest`. The root and local builds stay private; only the protected release
12
+ job makes the tested build publishable. See [releasing](docs/releasing.md)
13
+ and [release security](docs/release-security.md).
16
14
  The source uses the [MIT license](LICENSE), copyright ciqol contributors.
17
15
 
18
16
  ## Local development
@@ -25,6 +23,14 @@ npm ci
25
23
  npm run verify
26
24
  ```
27
25
 
26
+ The committed `.npmrc` omits automatically installed peer dependencies. The
27
+ release planner calls the two official plugins directly, so their full engine
28
+ peer and its publisher dependencies are intentionally not installed. Audit and
29
+ planning enforce that absence. `npm ls` still reports the declared missing
30
+ engine peers; standalone plugin behavior is covered directly by the release
31
+ specs. Keep this install policy when refreshing the lockfile; do not use force
32
+ or legacy peer resolution. See [the dependency review](docs/dependencies.md).
33
+
28
34
  `npm run verify` is the complete local check: lint, strict source/spec type
29
35
  checks, build, unit/process tests and installed-package checks. To build without
30
36
  running the checks, use `npm run build`.
@@ -60,14 +66,27 @@ shipping them in JavaScript or declarations. Do not maintain separate handwritte
60
66
  ```sh
61
67
  npm run test:unit # colocated Vitest specs, no build required
62
68
  npm run test:watch # watch unit specs while editing
63
- npm run test:coverage # source unit coverage, text and lcov output
69
+ npm run test:coverage # source unit coverage, text, lcov and Cobertura output
64
70
  npm run test:integration # process checks, requires a build
65
71
  ```
66
72
 
67
73
  `npm test` runs unit and process tests. Mock or inject provider calls, credential
68
- helpers, browser launchers and process control in unit tests. Keep checks that
69
- spawn actual commands or temporary applications in `test/`. The unit coverage
74
+ lookup and process control in unit tests. Keep checks that spawn actual commands
75
+ or temporary applications in `test/`. The unit coverage
70
76
  report measures source and is separate from packed-package and process checks.
77
+ `npm run verify` enforces a 90% global floor for source statements, branches,
78
+ functions and lines. Worker and preload entry files stay in the denominator;
79
+ the CLI is checked separately by process and packed-package tests. CI retains
80
+ LCOV and Cobertura reports for seven days in the `resenv-coverage` artifact.
81
+ The official `actions/upload-code-coverage` action uploads Cobertura in a separate
82
+ job for same-repository PRs and `main` runs; other runs retain the local gate and
83
+ report artifacts. The main `ci` job groups audits, lint, types, coverage, build
84
+ and packed-package/process checks on the pinned Node version. CLI lookup checks
85
+ use injected helpers and temporary
86
+ dummy executables; they never access a developer credential store or
87
+ authenticated provider session.
88
+ The `smoke` matrix checks that same archive on Linux Node 20 and 22 and macOS
89
+ with the pinned Node version; it does not build or rerun source unit specs.
71
90
 
72
91
  Use normal npm lifecycle behavior locally and in CI. Review install hooks,
73
92
  including implicit native builds, when changing dependencies; record any
@@ -87,6 +106,8 @@ Pass an existing archive to test the same build CI uses:
87
106
  `npm run test:package -- /absolute/path/to/package.tgz`. For a final CI candidate,
88
107
  also pass its `release.json` as the second argument. The supplied package's
89
108
  name, version, executable mode and manifest are checked before runtime probes.
109
+ The README inside the archive must match the checkout, install from npm's
110
+ `latest` channel and avoid unpublished or local-archive installation guidance.
90
111
  `npm run verify` runs lint, typecheck, build and tests; `npm run release:check`
91
112
  also previews the package contents. Neither publishes or creates a tag.
92
113
 
@@ -109,7 +130,7 @@ npm run pack:local
109
130
  Install the resulting archive from the application's directory:
110
131
 
111
132
  ```sh
112
- npm install --save-exact /absolute/path/to/ciqol-resenv-0.1.0-beta.1.tgz
133
+ npm install --save-exact /absolute/path/to/ciqol-resenv-0.1.0.tgz
113
134
  ```
114
135
 
115
136
  This changes the application's manifest and lockfile. The archive must exist
@@ -127,22 +148,20 @@ local. Leave its manifest, lockfile and CI configuration unchanged.
127
148
 
128
149
  Rebuild resenv after source, metadata or documentation changes and restart the
129
150
  application. An application `npm ci` can remove the link; recreate it when needed.
130
- If you only created the package link, run its CLI directly:
151
+ Authenticate with the official Doppler CLI from the application's directory:
131
152
 
132
153
  ```sh
133
- node node_modules/@ciqol/resenv/bin/resenv.cjs login doppler
154
+ doppler login
134
155
  ```
135
156
 
136
- For login from the resenv checkout, use
137
- `node dist/bin/resenv.cjs login doppler`. Real login is a manual integration
138
- check; automated tests must use injected or mocked credentials.
157
+ resenv reuses that login at runtime. Automated tests use injected or mocked
158
+ credentials; real login is a manual integration check owned by Doppler's CLI.
139
159
 
140
160
  ## Optional integration checks
141
161
 
142
162
  `npm run bench` measures fresh-process startup, a plain 200-variable environment,
143
163
  and a mocked provider with eight distinct lookups. It needs a build first and
144
164
  does not contact a provider. Timings are observations, not CI pass/fail thresholds.
145
- See [the review](docs/security-performance-review.md) for the recorded comparison.
146
165
 
147
166
  With an existing Nx installation:
148
167
 
@@ -168,7 +187,8 @@ enable that option; the CLI passes the returned copy to its child process.
168
187
 
169
188
  Add regression tests for changed behavior and avoid real provider calls. Use
170
189
  Conventional Commit subjects, for example `fix(resolver): preserve empty values`.
171
- Dependency changes require an updated [review record](docs/dependencies.md).
190
+ Review dependency changes under the [dependency policy](docs/dependencies.md)
191
+ and put the evidence in the PR. Update the guide when a lasting choice changes.
172
192
 
173
193
  CODEOWNERS identifies the maintainer. Keep bug reports and pull requests focused
174
194
  on observable behavior and include the checks run. Weekly dependency updates are
@@ -177,6 +197,8 @@ grouped, with major version updates reviewed separately; bots cannot merge them.
177
197
  Keep the README focused on installation, authentication, application integration,
178
198
  API behavior and troubleshooting. Put contributor tooling, test commands and
179
199
  local development instructions here; detailed internals belong in `docs/`.
200
+ Keep docs current rather than appending session logs, dated audit reports or
201
+ test transcripts. Record run results in PRs and CI.
180
202
 
181
203
  Security reports follow [SECURITY.md](SECURITY.md). Publication and repository
182
204
  visibility changes require a separate maintainer decision; see the
package/README.md CHANGED
@@ -20,45 +20,60 @@ Resolution happens once at startup. It does not watch the provider for changes;
20
20
  restart the application to fetch updated values. No filename is passed to
21
21
  resenv, and dotenv files are never rewritten.
22
22
 
23
- Requires Node.js 20 or later. This is an experimental beta, currently distributed
24
- as a package archive; it has not been published to npm.
23
+ Requires Node.js 20 or later. The package is experimental and remains in initial
24
+ development; its API may change between 0.x releases.
25
+
26
+ Background: [A new Git worktree shouldn't mean another copy of your secrets](https://dev.to/titanron/a-new-git-worktree-shouldnt-mean-another-copy-of-your-secrets-424).
25
27
 
26
28
  ## Install
27
29
 
28
- Install the archive supplied by the maintainer in your application project:
30
+ Install in your application project:
29
31
 
30
32
  ```sh
31
- npm install --save-exact /path/to/ciqol-resenv-0.1.0-beta.1.tgz
33
+ npm install --save-exact @ciqol/resenv
32
34
  ```
33
35
 
34
- This records resenv in your project's manifest and lockfile. Keep the archive
35
- available wherever that project installs dependencies. Once the beta is
36
- published to npm, you will be able to install `@ciqol/resenv@beta` instead.
36
+ This records the version selected by npm's `latest` tag in your project's
37
+ manifest and lockfile.
37
38
 
38
39
  The CLI examples below use `npx --no-install` to run the copy installed in your
39
40
  project. In an npm script, use `resenv` directly.
40
41
 
41
42
  ## Use with Doppler
42
43
 
44
+ Doppler manages application secrets. If you're new to it, start with
45
+ [Doppler's introduction](https://docs.doppler.com/docs/start).
46
+
43
47
  ### 1. Authenticate locally
44
48
 
45
- After installing the package, run:
49
+ Follow the [official Doppler CLI installation guide](https://docs.doppler.com/docs/cli#installation)
50
+ for your operating system, then log in from your application directory:
46
51
 
47
52
  ```sh
48
- npx --no-install resenv login doppler
53
+ doppler login
49
54
  ```
50
55
 
51
- Each developer authenticates with their own Doppler access. Secret reads use
52
- the HTTPS API. Credentials come from `DOPPLER_TOKEN`, the saved OS credential,
53
- or an existing Doppler CLI login, in that order. Startup reports missing access
54
- and stops; it does not open a login prompt.
56
+ Each developer authenticates with their own Doppler access.
57
+
58
+ > [!WARNING]
59
+ > Authentication and credential storage depend on the Doppler CLI's implementation
60
+ > and may vary by platform or CLI version. resenv only reuses its existing token;
61
+ > it does not manage login, logout or credential storage, or access the OS
62
+ > credential store directly.
63
+
64
+ At startup, resenv uses `DOPPLER_TOKEN` when supplied; otherwise it reads the
65
+ existing Doppler CLI token for the application's working directory. It then
66
+ fetches only referenced secrets through the HTTPS API. Missing credentials or
67
+ access stop startup without opening a browser or prompting for login.
68
+
69
+ For CI and other unattended environments, supply `DOPPLER_TOKEN` through your
70
+ runner's secret facility; the CLI is not needed in that mode. See
71
+ [Doppler authentication and platform details](docs/doppler.md), including
72
+ migration from the earlier resenv-managed login.
55
73
 
56
- Native login uses the browser flow observed in Doppler's official MCP client
57
- and saves the token in macOS Keychain or Linux Secret Service. The CLI is a
58
- fallback; the browser protocol is not a public OAuth contract and fresh native
59
- login is not yet verified end to end. Windows needs `DOPPLER_TOKEN` because
60
- native login/storage is not implemented there. See
61
- [Doppler login and platform details](docs/doppler.md).
74
+ Need another secrets provider, operating system or CLI installation method?
75
+ [Request support](https://github.com/ciqol/resenv/issues/new) with your use case
76
+ and platform details.
62
77
 
63
78
  ### 2. Declare references in the environment your app already loads
64
79
 
@@ -94,7 +109,8 @@ npx --no-install resenv run -- node app.mjs
94
109
 
95
110
  The wrapper supplies resolved values to the child process. It inherits
96
111
  stdin/stdout/stderr, preserves exit status and forwards SIGINT, SIGTERM and
97
- SIGHUP to that child.
112
+ SIGHUP to that child. On POSIX, the child runs in its own session so terminal
113
+ signals reach it only through the wrapper.
98
114
 
99
115
  ## Nx integration
100
116
 
package/SECURITY.md CHANGED
@@ -9,22 +9,24 @@ any credentials supplied to it.
9
9
 
10
10
  Doppler mode uses a fixed HTTPS API origin, disables redirects and fetches only
11
11
  referenced secret names. It does not execute shell expressions. Credentials
12
- come from an explicit token, resenv's OS credential store or a fixed Doppler CLI
13
- helper. An API rejection never triggers a switch to another identity. Native
14
- browser login uses endpoints observed in Doppler's official MCP client rather
15
- than a public third-party OAuth contract. See [Doppler details](docs/doppler.md)
16
- for fallback rules, credential scope and platform limits.
12
+ come from an explicit `DOPPLER_TOKEN` or the current directory's existing
13
+ Doppler CLI login. A fixed CLI helper captures that token privately; resenv
14
+ does not parse Doppler's config files. An API rejection never triggers a switch
15
+ to another identity. The official Doppler CLI owns browser authorization,
16
+ token storage, login and logout. See [Doppler details](docs/doppler.md)
17
+ for lookup rules, credential scope, migration and platform limits.
17
18
  The canonical `resolve` preload and `run` command select Doppler only for
18
19
  `doppler://` values. They do not infer shell execution from `$()` values.
19
- Startup does not initiate login; the separate login command rejects CI and
20
- noninteractive sessions.
20
+ Startup does not initiate login. Use `doppler login` separately for local
21
+ development or supply a suitably scoped token for unattended runs.
21
22
 
22
- The local credential store does not prevent code running as the same user from
23
- requesting credentials. resenv does not automatically remove an explicitly
24
- supplied `DOPPLER_TOKEN` from the application environment. Interactive login
25
- displays a temporary human authorization code; tokens and the polling code are
26
- not logged. The Node preload's private worker exchanges credentials and results
27
- over captured pipes, never process arguments or dotenv files.
23
+ Doppler's credential storage does not isolate credentials from other code
24
+ running as the same user with access to them. resenv does not read or write the
25
+ OS credential store directly or persist login tokens or fetched secrets.
26
+ It does not automatically remove an explicitly supplied `DOPPLER_TOKEN` from
27
+ the application environment. Tokens, captured CLI output and provider response
28
+ bodies are not logged. The Node preload's private worker exchanges credentials
29
+ and results over captured pipes, never process arguments or dotenv files.
28
30
 
29
31
  Command mode executes trusted configuration with the current user's
30
32
  permissions. Enable it only for configuration you have reviewed. Commands can
@@ -57,17 +59,15 @@ all changed properties; read-only properties, setters and Proxy targets are
57
59
  rejected before any values are committed. Use `resolve()` to obtain a copy
58
60
  when the input is not a writable environment record.
59
61
 
60
- This experimental package has not had an independent security audit. Validation
61
- evidence and platform limits are recorded in [docs/validation.md](docs/validation.md).
62
+ This experimental package has not had an independent security audit. See
63
+ [verification](CONTRIBUTING.md) and [Doppler platform limits](docs/doppler.md).
62
64
 
63
65
  ## Reporting a vulnerability
64
66
 
65
67
  Do not open a public issue containing a vulnerability, credentials or sensitive
66
- output. Open the repository's Security tab and use "Report a vulnerability"
67
- when available. If that option is unavailable, contact the maintainer through
68
- an existing private channel. Before the public transition is complete,
69
- maintainers must enable GitHub private vulnerability reporting and verify that
70
- it is available.
68
+ output. [Report a vulnerability privately](https://github.com/ciqol/resenv/security/advisories/new)
69
+ through GitHub. If that option is unavailable, contact the maintainer through
70
+ an existing private channel.
71
71
 
72
72
  Include affected versions, platform, impact and a minimal reproduction using
73
73
  dummy data. Never include production tokens, private keys or environment dumps.
package/bin/resenv.cjs CHANGED
@@ -6,32 +6,15 @@ var node_os = require('node:os');
6
6
  var assertSafeDiagnostics = require('../assert-safe-diagnostics.cjs');
7
7
  var preload = require('../preload.cjs');
8
8
  var index = require('../index.cjs');
9
- var auth = require('../doppler/auth.cjs');
10
- var system = require('../doppler/system.cjs');
11
- var references = require('../doppler/references.cjs');
12
9
  var values = require('../values.cjs');
13
10
 
14
- const usage = 'Usage: resenv run [--commands] -- <command> [arguments...]\n resenv login [doppler] [--cli] [--no-browser]\n resenv logout doppler\nInfer secret providers from values already in the environment.\n--doppler remains accepted for compatibility.\n--commands enables whole-value $(command) expressions using /bin/sh.\n';
15
- async function main() {
11
+ const usage = 'Usage: resenv run [--commands] -- <command> [arguments...]\nInfer secret providers from values already in the environment.\nManage authentication with doppler login and doppler logout, or supply DOPPLER_TOKEN.\n--doppler remains accepted for compatibility.\n--commands enables whole-value $(command) expressions using /bin/sh.\n';
12
+ function main() {
16
13
  const args = process.argv.slice(2);
17
14
  if (args.length === 1 && ['--help', '-h'].includes(args[0] || '')) {
18
15
  process.stdout.write(usage);
19
16
  return;
20
17
  }
21
- const explicitProvider = args[1] === 'doppler';
22
- if (args[0] === 'login' && args.slice(explicitProvider ? 2 : 1).every(arg => ['--cli', '--no-browser'].includes(arg))) {
23
- if (!explicitProvider && !references.references(process.env).length) {
24
- process.stderr.write('Nothing to handle: no cloud secret references in the environment. Load your target environment first, or use resenv login doppler.\n');
25
- return;
26
- }
27
- await auth.login({ cli: args.includes('--cli'), noBrowser: args.includes('--no-browser') });
28
- return;
29
- }
30
- if (args.length === 2 && args[0] === 'logout' && args[1] === 'doppler') {
31
- system.createSystem().remove();
32
- process.stderr.write('Removed the resenv credential. Doppler CLI credentials and DOPPLER_TOKEN are unchanged; revoke the token in Doppler to invalidate it.\n');
33
- return;
34
- }
35
18
  let commands = false;
36
19
  while (args[0] === 'run' && ['--commands', '--doppler'].includes(args[1] || '')) {
37
20
  if (args[1] === '--commands')
@@ -55,11 +38,23 @@ async function main() {
55
38
  return;
56
39
  }
57
40
  assertSafeDiagnostics.assertSafeDiagnostics();
58
- const child = node_child_process.spawn(command, commandArgs, { env, stdio: 'inherit', shell: false });
41
+ // Isolate the child from terminal group signals; the wrapper forwards them once.
42
+ const child = node_child_process.spawn(command, commandArgs, { env, stdio: 'inherit', shell: false, detached: process.platform !== 'win32' });
59
43
  const signals = ['SIGINT', 'SIGTERM', 'SIGHUP'];
60
44
  const handlers = new Map();
61
45
  for (const signal of signals) {
62
- const handler = () => { child.kill(signal); };
46
+ const handler = () => {
47
+ try {
48
+ if (process.platform !== 'win32' && child.pid)
49
+ process.kill(-child.pid, signal);
50
+ else
51
+ child.kill(signal);
52
+ }
53
+ catch (err) {
54
+ if (!values.hasErrorCode(err, 'ESRCH'))
55
+ throw err;
56
+ }
57
+ };
63
58
  handlers.set(signal, handler);
64
59
  process.on(signal, handler);
65
60
  }
@@ -85,7 +80,10 @@ async function main() {
85
80
  }
86
81
  });
87
82
  }
88
- void main().catch(err => {
83
+ try {
84
+ main();
85
+ }
86
+ catch (err) {
89
87
  process.stderr.write(`${index.formatError(err)}\n`);
90
88
  process.exitCode = 1;
91
- });
89
+ }
package/docs/build.md CHANGED
@@ -71,7 +71,7 @@ Public options use type aliases and readonly properties. `Environment` stays
71
71
  mutable for optional in-place replacement. `resolve()` always returns a new
72
72
  `Environment`, with input replacement enabled only by `{ replace: true }`. It
73
73
  does not preserve a generic input type: resolved strings cannot retain a
74
- literal reference type. This intentionally tightens the unpublished API.
74
+ literal reference type.
75
75
  ESLint uses type information and enforces the selected style rules. The compiler
76
76
  checks all library/CLI source, colocated unit specs and both consumer formats with `strict` and
77
77
  `noUncheckedIndexedAccess`. Provider responses and parsed worker input start as
@@ -90,11 +90,11 @@ overrides and a package-wide `type` field. Explicit `.mjs` and `.cjs` extensions
90
90
  select the output format. README, the MIT `LICENSE`, contributor/security guides
91
91
  and docs are copied into the distribution and included in the archive.
92
92
 
93
- Ordinary local builds retain `private: true`. The CI build accepts a validated
94
- release plan and sets its computed version, removing `private` before the final
95
- archive is packed and tested. The development root stays private. Earlier
96
- builds kept both manifests private while the package was an unpublished
97
- prototype. There is no publishing workflow.
93
+ Ordinary local and CI builds retain `private: true`. Only the protected release
94
+ job makes the tested build publishable and lets the npm plugin set its version.
95
+ The development root stays private. Release consumer checks validate the final
96
+ versioned package before direct publication; see
97
+ [releasing](releasing.md).
98
98
 
99
99
  `npm run pack:local` packs the generated directory. `npm run test:package`
100
100
  instead packs into a temporary directory and installs it offline into a clean
@@ -114,23 +114,26 @@ consumer artifact is rebuilt in place by these tests.
114
114
 
115
115
  ## CI
116
116
 
117
- The `build` job uses normal npm lifecycle behavior, checks dependency advisories and
118
- signatures, runs typed lint, implementation/spec checks and Vitest source coverage,
119
- builds once and uploads the packed
120
- archive. The `test` matrix downloads that artifact, verifies the installed
121
- package, extracts the same build
122
- for the process tests, and never recompiles it. The matrix checks Node 20
123
- and 22 on Linux plus the `.nvmrc` version on Linux and macOS.
124
-
125
- Jobs have read-only repository permissions and receive no application or
126
- registry secrets. Missing artifacts and digest mismatches fail the run. The
127
- archive is retained for seven days for maintainer review. The workflow has no
128
- publish or deploy step. Hosted results must be observed before treating the
129
- workflow definition as a passing CI run.
130
-
131
- Checkout and setup/download actions use full commit pins. The GitHub-owned
132
- upload action uses the Tashtit exception for a complete release tag; major tags
133
- and third-party movable refs are not permitted. Installs use normal npm lifecycle
134
- behavior, and dependency updates require an install-hook review. Build commands
135
- run explicitly.
136
- See the [maturity evaluation](maturity.md) for the static catalog results.
117
+ The `ci` job checks advisories and registry signatures, lint, types and source
118
+ coverage, then builds and packs once. It tests the private archive and processes
119
+ on the pinned Node version. The `smoke` matrix tests that archive on Linux Node
120
+ 20/22 and macOS with pinned Node 24; it never recompiles source. Coverage runs
121
+ once, with a separate job uploading the report on same-repository PRs and main.
122
+ Required archives and coverage reports are retained for seven days.
123
+
124
+ After a merge to protected `main`, `release` downloads the tested build and runs
125
+ semantic-release with the official npm/GitHub plugins. The npm plugin prepares
126
+ version metadata and a tarball; the local verification hook checks the final
127
+ package's consumer contract before publication. Source is never rebuilt in the
128
+ release job. The official npm plugin publishes the prepared directory with no
129
+ package lifecycle scripts. See [releasing](releasing.md) for the one-time 0.1.0
130
+ bootstrap and failure handling.
131
+
132
+ PRs and manual dispatches cannot publish. Release permissions are limited to
133
+ GitHub contents write and OIDC. Package and registry secrets are absent from
134
+ validation jobs. Every checkout disables persisted credentials, and the release
135
+ token is scoped to the release step. Official GitHub actions use exact release
136
+ tags; other remote actions require commit SHAs.
137
+
138
+ The protected branch requires `ci`, `coverage`, all three `smoke` contexts and
139
+ `Analyze (javascript-typescript)`. Keep these names stable when editing CI.