Skip to content

refactor secrets (drop the lib)#385

Merged
vicb merged 1 commit into
masterfrom
vicb/secrets
Jan 4, 2026
Merged

refactor secrets (drop the lib)#385
vicb merged 1 commit into
masterfrom
vicb/secrets

Conversation

@vicb
Copy link
Copy Markdown
Owner

@vicb vicb commented Jan 4, 2026

Summary by Sourcery

Inline environment secret handling across apps using a shared global SECRETS definition and remove the dedicated secrets library and its Nx dependencies.

Enhancements:

  • Load secrets from a shared secrets.env.local file at webpack build time and expose them via a global SECRETS object in multiple Node apps.
  • Replace imports and usages of the removed secrets library with references to the global SECRETS object across server, fetcher, proxy, tiles, misc, and run code paths.
  • Adjust fxc-server and fetcher validators and routes to accept tokens via parameters or SECRETS while preserving existing behavior.
  • Switch the proxy app entrypoint from main.ts to index.ts and update related configuration.
  • Define TypeScript typings for the SECRETS global and share secrets.env.local as an Nx shared global for tasks.
  • Stub SECRETS values in Jest configs where needed for tests.

Build:

  • Remove Nx project dependencies on the secrets library from app build and test targets and simplify the dev script by dropping the secrets build step.
  • Add dotenvx-based secret parsing in webpack configs and introduce new dependencies required for the updated build setup.

Chores:

  • Delete the obsolete secrets library and its associated configuration and assets from the repository.

Summary by CodeRabbit

  • Chores
    • Refactored internal secrets management infrastructure to use build-time environment injection instead of runtime module imports. No changes to application functionality or user-facing features.

✏️ Tip: You can customize this high-level summary in your review settings.

@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented Jan 4, 2026

Reviewer's Guide

Refactors secret handling by removing the @flyxc/secrets library and replacing it with a global SECRETS object injected at build time from secrets.env.local via webpack, updating all consumers, build configs, and tests accordingly.

Sequence diagram for webpack secrets loading and SECRETS definition

sequenceDiagram
  actor Dev
  participant Nx as Nx
  participant Webpack as Webpack
  participant Config as WebpackConfig_js
  participant Fs as NodeFs
  participant Dotenvx as DotenvxParse
  participant DefinePlugin as webpack_DefinePlugin
  participant Bundle as CompiledBundle
  participant Runtime as NodeRuntime

  Dev->>Nx: run build app
  Nx->>Webpack: invoke with config
  Webpack->>Config: evaluate composePlugins(withNx, config)

  Config->>Fs: existsSync(secrets.env.local)
  Fs-->>Config: true

  Config->>Fs: readFileSync(secrets.env.local)
  Fs-->>Config: fileContents

  Config->>Dotenvx: parse(fileContents)
  Dotenvx-->>Config: envConfig key/value map

  Config->>Config: build secrets map SECRETS.KEY = JSON.stringify(value)
  Config->>DefinePlugin: new DefinePlugin(secrets)
  DefinePlugin-->>Webpack: plugin instance
  Webpack->>Bundle: replace SECRETS.* at compile time

  Dev->>Runtime: run compiled app
  Runtime->>Bundle: execute code
  Bundle-->>Runtime: SECRETS global values in place of literals
Loading

Class diagram for FlyMe validation refactor using injected token

classDiagram
  class TrackerEntity {
    +flyme any
    +inreach any
    +skylines any
  }

  class TrackerModel {
    +flyme any
    +inreach any
    +skylines any
  }

  class FlyMeValidator {
    -currentEnabled boolean
    -currentAccount string
    -token string
    +constructor(flyme TrackerEntity, token string)
    +validate(tracker TrackerModel) boolean
    +message string
  }

  class InreachValidator {
    -currentEnabled boolean
    -currentAccount string
    +constructor(inreach TrackerEntity)
    +validate(tracker TrackerModel) boolean
    +message string
  }

  class FlyMeApi {
    +getFlyMeId(username string, token string) Promise~string|undefined~
  }

  class Binder {
    +model TrackerModel
    +for(field any) ValidatorChain
  }

  class ValidatorChain {
    +addValidator(validator any) void
  }

  class LiveTrackRoute {
    +createOrUpdateLiveTrack(req Request, res Response, email string, googleId string, redis Redis) Promise~void~
  }

  FlyMeValidator ..> FlyMeApi : uses
  LiveTrackRoute ..> FlyMeValidator : creates with SECRETS.FLYME_TOKEN
  LiveTrackRoute ..> InreachValidator : creates
  LiveTrackRoute --> Binder : configures validators
  Binder --> TrackerModel
  TrackerEntity <.. LiveTrackRoute : loads existing entity
Loading

File-Level Changes

Change Details Files
Inject secrets into Node apps via webpack DefinePlugin using secrets.env.local and a global SECRETS object.
  • Add webpack, fs, and @dotenvx/dotenvx imports to webpack configs for Node apps.
  • Read secrets.env.local at build time, parse it, and construct a key/value map prefixed with SECRETS., stringifying values.
  • Ensure config.plugins is initialized and push a webpack.DefinePlugin instance with the SECRETS map for each app.
apps/fetcher/webpack.config.js
apps/fxc-tiles/webpack.config.js
apps/misc/webpack.config.js
apps/proxy/webpack.config.js
apps/run/webpack.config.js
apps/fxc-server/webpack.config.js
Replace usages of the Secrets library with the global SECRETS object throughout server, worker, and tooling code.
  • Swap Secrets.X references for SECRETS.X in server routes, proxy, fetchers, background jobs, and misc scripts.
  • Adjust authentication, API calls, and configuration that rely on secrets (Redis URL, OAuth, MailerSend, Zoleo, MeshBir, Flymaster, Xcontest, Geonames, APRS, Aviant, proxy key).
apps/fxc-server/src/main.ts
apps/fxc-server/src/app/routes/live-track.ts
apps/fxc-server/src/app/routes/zoleo.ts
apps/fxc-server/src/app/routes/session.ts
apps/fxc-server/src/app/routes/meshbir.ts
apps/fetcher/src/app/trackers/flymaster.ts
apps/fetcher/src/app/trackers/xcontest.ts
apps/fetcher/src/app/trackers/flyme.ts
apps/fetcher/src/app/trackers/inreach.ts
apps/fetcher/src/app/trackers/refresh.ts
apps/fetcher/src/app/ufos/aviant.ts
apps/fetcher/src/fetcher.ts
apps/fxc-tiles/src/app/airspaces/download-openaip.ts
apps/misc/src/app/email_inreach.ts
apps/proxy/src/index.ts
apps/run/src/app/process.ts
Update validation and routing logic to accept injected FlyMe token instead of reading it from the Secrets library directly.
  • Change getFlyMeId to accept a token parameter instead of closing over Secrets.FLYME_TOKEN.
  • Update FlyMeValidator to accept a token in its constructor and pass it through to getFlyMeId.
  • Inject SECRETS.FLYME_TOKEN when constructing FlyMeValidator in live-track route logic.
  • Rename the createOrUpdateLiveTrack parameter from token to googleId where it represents the Google identifier rather than an auth token.
libs/common-node/src/lib/validators.ts
apps/fxc-server/src/app/routes/live-track.ts
Remove the @flyxc/secrets Nx library and associated build dependencies, replacing it with a root-level secrets file and type declarations.
  • Delete the libs/secrets project (source, config, webpack, package files, and .gitignore) and the rollup.build.css file that was part of its build.
  • Move the secrets .env file to the workspace root as secrets.env and introduce secrets.env.local as a shared global for Nx.
  • Remove Nx project dependsOn entries that built the secrets project before app build/test targets and simplify the dev script by dropping nx build secrets.
  • Add a TypeScript declaration file secrets.d.ts declaring the SECRETS shape used across the codebase.
libs/secrets/README.md
libs/secrets/project.json
libs/secrets/src/index.ts
libs/secrets/src/lib/secrets.ts
libs/secrets/webpack.config.js
libs/secrets/eslint.config.js
libs/secrets/package.json
libs/secrets/package-lock.json
libs/secrets/tsconfig.json
libs/secrets/tsconfig.lib.json
libs/secrets/.gitignore
libs/secrets/.env
rollup.build.css
secrets.env
nx.json
apps/proxy/project.json
apps/fetcher/project.json
apps/fxc-server/project.json
apps/fxc-tiles/project.json
apps/misc/project.json
apps/run/project.json
secrets.d.ts
package.json
Align tests, TypeScript configs, and entrypoints with the new secrets mechanism and project layout.
  • Define a SECRETS global in Jest config for fxc-server and fetcher so tests have a minimal secret set.
  • Update Nx sharedGlobals to include secrets.env.local so jest/babel/webpack can see consistent environment.
  • Rename proxy app entry from main.ts to index.ts and update the corresponding project.json main option.
  • Add new dependencies (@dotenvx/dotenvx and baseline-browser-mapping) and update package-lock accordingly.
  • Touch tsconfig files for multiple apps (likely to ensure correct typing of the global SECRETS via the new declaration).
apps/fxc-server/jest.config.ts
apps/fetcher/jest.config.ts
nx.json
apps/proxy/src/index.ts
apps/proxy/project.json
package.json
package-lock.json
apps/fetcher/tsconfig.app.json
apps/fetcher/tsconfig.spec.json
apps/fxc-server/tsconfig.app.json
apps/fxc-server/tsconfig.spec.json
apps/fxc-tiles/tsconfig.app.json
apps/misc/tsconfig.app.json
apps/proxy/tsconfig.app.json
apps/run/tsconfig.app.json

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 4, 2026

Walkthrough

This PR removes the libs/secrets library and migrates the codebase from runtime Secrets imports to a globally injected SECRETS constant loaded at build time via Webpack DefinePlugin, with TypeScript definitions provided by a new secrets.d.ts file.

Changes

Cohort / File(s) Summary
Secrets Library Removal
libs/secrets/...
Deletes entire library: package.json, project.json, webpack.config.js, tsconfig.json, tsconfig.lib.json, eslint.config.js, README.md, src/index.ts, src/lib/secrets.ts. Removes both the public API export and the underlying implementation.
Global SECRETS Declaration
secrets.d.ts
New TypeScript declaration file exporting SECRETS constant with typed string fields for all secret keys (GEONAMES, REDIS_URL, GOOGLE_OAUTH_ID, SESSION_SECRET, FLYME_TOKEN, etc.).
Webpack Secret Injection
apps/*/webpack.config.js, apps/proxy/webpack.config.js (5 files)
Adds runtime logic to read secrets.env.local, parse environment variables, validate file existence, and inject secrets via webpack.DefinePlugin under SECRETS.* keys as JSON-stringified values. Imports webpack, fs, and @dotenvx/dotenvx.parse.
Fetcher App Secret Migration
apps/fetcher/src/app/trackers/flymaster.ts, flyme.ts, inreach.ts, refresh.ts, xcontest.ts
apps/fetcher/src/app/ufos/aviant.ts
apps/fetcher/src/fetcher.ts
Removes Secrets imports and replaces all Secrets.* references with global SECRETS.* for API credentials and configuration values.
FXC-Server App Secret Migration
apps/fxc-server/src/app/routes/live-track.ts, meshbir.ts, session.ts, zoleo.ts
apps/fxc-server/src/main.ts
Removes Secrets imports and replaces all Secrets.* references with global SECRETS.*. Live-track.ts also refactors function signature: renames token parameter to googleId and updates FlyMeValidator constructor to accept token parameter.
FXC-Tiles & Misc App Secret Migration
apps/fxc-tiles/src/app/airspaces/download-openaip.ts
apps/misc/src/app/email_inreach.ts
apps/proxy/src/index.ts
apps/run/src/app/process.ts
Removes Secrets imports and updates all Secrets.* references to global SECRETS.* for API tokens and configuration.
Validator Library Refactoring
libs/common-node/src/lib/validators.ts
Removes Secrets import; updates getFlyMeId() signature to accept token parameter; updates FlyMeValidator constructor to accept and store token parameter; passes token to getFlyMeId() instead of reading from Secrets.
TypeScript Configuration Updates
apps/*/tsconfig.app.json, apps/*/tsconfig.spec.json (8 files)
Adds ../../secrets.d.ts to include arrays across fetcher, fxc-server, fxc-tiles, misc, proxy, and run apps for both lib and spec configs.
Build Configuration Updates
apps/fetcher/jest.config.ts, apps/fxc-server/jest.config.ts
Adds globals: { SECRETS: {} } to Jest config for fetcher; adds globals: { SECRETS.ADMINS: 'admin' } for fxc-server.
Project Dependency Removals
apps/fetcher/project.json, apps/fxc-server/project.json, apps/fxc-tiles/project.json, apps/misc/project.json, apps/proxy/project.json, apps/run/project.json
Removes dependsOn: ["secrets:build"] from build and test targets, decoupling apps from the secrets library build.
Package & Environment Configuration
.gitignore, package.json, nx.json, secrets.env
Adds *.env.local to .gitignore; removes "nx build secrets &&" from dev script; adds @dotenvx/dotenvx and baseline-browser-mapping dependencies; updates nx.json namedInputs.sharedGlobals to include {workspaceRoot}/secrets.env.local; renames XCONTEXT_JWT to XCONTEST_JWT in secrets.env.
CSS Cleanup
rollup.build.css
Removes comprehensive styling block for #plugin-windy-plugin-sounding including mobile variations and chart selectors.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Separate front and API #221: Directly inverse—adds the Secrets library and introduces the imports/usages that this PR removes; both touch libs/secrets, @flyxc/secrets imports, and secret key definitions.
  • misc #379: Modifies apps/fetcher/src/fetcher.ts in parallel; one PR removes supporter-sync logic while this refactors Secrets→SECRETS and Redis initialization in the same file.
  • Add support for Meshtastic (Bircom) #303: Modifies meshbir-related route handlers (apps/fxc-server/src/app/routes/meshbir.ts); this PR converts the Secrets references to SECRETS in those same files.

Poem

🐰 Secrets once lived in a library so fine,
But now they're injected at build-time divine!
DefinePlugin weaves them through webpack's embrace,
While SECRETS global fills every code place.
No more runtime imports—just globals so bright,
This refactor hops smoothly from dusk into light! 🌙✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'refactor secrets (drop the lib)' accurately describes the main change: removing the secrets library and refactoring how secrets are handled throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch vicb/secrets

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The logic to load secrets.env.local and build the DefinePlugin map is duplicated across every app’s webpack.config.js; consider extracting this into a shared helper (e.g., in a small util module) so the path, parsing, and error behavior stay consistent and easier to change in one place.
  • Throwing an error when secrets.env.local is missing in every webpack config will cause all builds/tests to fail even in contexts that may not require secrets (e.g., CI or local utilities); you might want to make the path configurable or allow a more graceful fallback for non‑prod environments.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The logic to load `secrets.env.local` and build the `DefinePlugin` map is duplicated across every app’s `webpack.config.js`; consider extracting this into a shared helper (e.g., in a small util module) so the path, parsing, and error behavior stay consistent and easier to change in one place.
- Throwing an error when `secrets.env.local` is missing in every webpack config will cause all builds/tests to fail even in contexts that may not require secrets (e.g., CI or local utilities); you might want to make the path configurable or allow a more graceful fallback for non‑prod environments.

## Individual Comments

### Comment 1
<location> `apps/fxc-tiles/webpack.config.js:8-17` </location>
<code_context>
 module.exports = composePlugins(withNx(), (config) => {
-  // Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file.
-  // See: https://nx.dev/recipes/webpack/webpack-config-setup
+  const secretsPath = 'secrets.env.local';
+  /** @type {Record<string, string>} */
+  const secrets = {};
+
+  if (!fs.existsSync(secretsPath)) {
+    throw new Error(`Secrets file not found at path: ${secretsPath}`);
+  }
+
+  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));
+
+  for (const [key, value] of Object.entries(envConfig)) {
+    secrets[`SECRETS.${key}`] = JSON.stringify(value);
+  }
+
+  config.plugins = config.plugins || [];
+  config.plugins.push(new webpack.DefinePlugin(secrets));
   return config;
 });
</code_context>

<issue_to_address>
**suggestion:** The secrets loading + DefinePlugin wiring is duplicated across multiple webpack configs and could be centralized.

This secrets loading + `DefinePlugin` setup is repeated in each app’s webpack config, which makes it easy for behavior to drift between apps (different paths, parsing rules, or keys). Consider extracting it into a shared helper (e.g. `createSecretsDefinePlugin()` in a common build util) or a small custom webpack plugin so the behavior is defined once and reused.

Suggested implementation:

```javascript
const { composePlugins, withNx } = require('@nx/webpack');
const { createSecretsDefinePlugin } = require('../../tools/webpack/create-secrets-define-plugin');

// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
  config.plugins = config.plugins || [];
  config.plugins.push(createSecretsDefinePlugin());
  return config;
});

```

To fully implement the centralization and remove duplication, you should add a shared helper (and then migrate other apps to use it):

1. Create `tools/webpack/create-secrets-define-plugin.js` (or adjust the path to match your repo conventions) with something like:

```js
const fs = require('node:fs');
const webpack = require('webpack');
const { parse } = require('@dotenvx/dotenvx');

/**
 * Creates a DefinePlugin instance that exposes secrets from a dotenv-style file.
 *
 * @param {object} options
 * @param {string} [options.path='secrets.env.local'] - Path to the secrets file, relative to workspace root.
 * @returns {import('webpack').DefinePlugin}
 */
function createSecretsDefinePlugin(options = {}) {
  const secretsPath = options.path ?? 'secrets.env.local';

  /** @type {Record<string, string>} */
  const secrets = {};

  if (!fs.existsSync(secretsPath)) {
    throw new Error(`Secrets file not found at path: ${secretsPath}`);
  }

  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));

  for (const [key, value] of Object.entries(envConfig)) {
    secrets[`SECRETS.${key}`] = JSON.stringify(value);
  }

  return new webpack.DefinePlugin(secrets);
}

module.exports = { createSecretsDefinePlugin };
```

2. Update any other app-specific `webpack.config.js` files that currently inline the same secrets + `DefinePlugin` logic to instead:
   - Remove their direct `fs`, `webpack`, and `@dotenvx/dotenvx` imports.
   - Import `createSecretsDefinePlugin` from the shared helper.
   - Push `createSecretsDefinePlugin()` into `config.plugins` just like in `apps/fxc-tiles/webpack.config.js`.

3. If different apps need different secrets file paths, pass `{ path: 'custom/path.env' }` to `createSecretsDefinePlugin` where appropriate, keeping the logic centralized while allowing per-app configuration.
</issue_to_address>

### Comment 2
<location> `secrets.d.ts:1-10` </location>
<code_context>
+declare const SECRETS: {
+  GEONAMES: string;
+  REDIS_URL: string;
+  GOOGLE_OAUTH_ID: string;
+  GOOGLE_OAUTH_SECRET: string;
+  SESSION_SECRET: string;
+  FLYME_TOKEN: string;
+  ADMINS: string;
+  PROXY_KEY: string;
+  OPENAIP_KEY: string;
+  AVIANT_URL: string;
+  APRS_USER: string;
+  APRS_PASSWORD: string;
+  ZOLEO_UNLINK_URL: string;
+  ZOLEO_UNLINK_API_KEY: string;
+  ZOLEO_PUSH_USER: string;
+  ZOLEO_PUSH_PWD: string;
+  XCONTEST_JWT: string;
+  BUY_ME_A_COFFEE_TOKEN: string;
+  FLYMASTER_GROUP_ID: string;
+  FLYMASTER_GROUP_TOKEN: string;
+  MAILERSEND_TOKEN: string;
+  MESHBIR_AUTH_TOKEN: string;
+};
</code_context>

<issue_to_address>
**issue (bug_risk):** Typing all `SECRETS` fields as required can hide missing values at runtime, especially in tests.

In tests, only some of these fields are set on `SECRETS` (e.g. only `ADMINS` in `fxc-server`, `{}` in `fetcher`), so other properties will be `undefined` at runtime while typed as `string`. To avoid this mismatch, either (a) make these properties optional and enforce a safe access pattern, (b) ensure test globals stub all keys, or (c) narrow the `SECRETS` type for test builds to just the fields they actually provide.
</issue_to_address>

### Comment 3
<location> `apps/fxc-server/jest.config.ts:4-7` </location>
<code_context>
   displayName: 'fetcher',
   preset: '../../jest.preset.js',
-  globals: {},
+  globals: {
+    SECRETS: {},
+  },
</code_context>

<issue_to_address>
**suggestion (testing):** Jest `SECRETS` stub only defines `ADMINS`, which may lead to runtime `undefined` for other secrets accessed in tests.

Several runtime paths in `fxc-server` (Redis URL, OAuth, Zoleo, etc.) now read from `SECRETS`, so in tests these will see `undefined` despite the type being `string`. Please either populate all currently used secrets with dummy values here or add a small helper for safe secret access in tests (e.g., throwing if an expected secret is missing).

Suggested implementation:

```typescript
  globals: {
    // Defined secrets used during tests.
    // NOTE: Keep this in sync with all secrets accessed via SECRETS in fxc-server runtime code.
    SECRETS: {
      // existing
      ADMINS: 'admin',

      // Redis / cache
      REDIS_URL: 'redis://localhost:6379/0',

      // OAuth / Auth0 / OIDC (adjust keys to match actual usage)
      OAUTH_CLIENT_ID: 'test-oauth-client-id',
      OAUTH_CLIENT_SECRET: 'test-oauth-client-secret',
      OAUTH_ISSUER: 'https://example.test-issuer/',
      OAUTH_AUDIENCE: 'test-audience',

      // Zoleo or other 3rd-party integrations
      ZOLEO_API_KEY: 'test-zoleo-api-key',
      ZOLEO_API_BASE_URL: 'https://api.zoleo.test',

      // Miscellaneous commonly-used secrets (add/remove to match real keys)
      SESSION_SECRET: 'test-session-secret',
      JWT_SECRET: 'test-jwt-secret',
    },
  },

```

I only see the Jest config, not the full runtime code that reads from `SECRETS`. To fully implement the intent:

1. Enumerate all keys actually read from `SECRETS` in `fxc-server` (e.g., a `secrets.ts` module or any `SECRETS.X` usages).
2. Replace or augment the placeholder keys I added (`REDIS_URL`, `OAUTH_*`, `ZOLEO_*`, `SESSION_SECRET`, `JWT_SECRET`) so that this Jest stub exactly matches those keys.
3. Optionally, add a small helper in your secrets-access layer used by tests (e.g., `getSecret(key: keyof typeof SECRETS)`) which throws if a required secret is missing; this would enforce that the Jest `SECRETS` object stays in sync with runtime expectations.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +8 to +17
const secretsPath = 'secrets.env.local';
/** @type {Record<string, string>} */
const secrets = {};

if (!fs.existsSync(secretsPath)) {
throw new Error(`Secrets file not found at path: ${secretsPath}`);
}

const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: The secrets loading + DefinePlugin wiring is duplicated across multiple webpack configs and could be centralized.

This secrets loading + DefinePlugin setup is repeated in each app’s webpack config, which makes it easy for behavior to drift between apps (different paths, parsing rules, or keys). Consider extracting it into a shared helper (e.g. createSecretsDefinePlugin() in a common build util) or a small custom webpack plugin so the behavior is defined once and reused.

Suggested implementation:

const { composePlugins, withNx } = require('@nx/webpack');
const { createSecretsDefinePlugin } = require('../../tools/webpack/create-secrets-define-plugin');

// Nx plugins for webpack.
module.exports = composePlugins(withNx(), (config) => {
  config.plugins = config.plugins || [];
  config.plugins.push(createSecretsDefinePlugin());
  return config;
});

To fully implement the centralization and remove duplication, you should add a shared helper (and then migrate other apps to use it):

  1. Create tools/webpack/create-secrets-define-plugin.js (or adjust the path to match your repo conventions) with something like:
const fs = require('node:fs');
const webpack = require('webpack');
const { parse } = require('@dotenvx/dotenvx');

/**
 * Creates a DefinePlugin instance that exposes secrets from a dotenv-style file.
 *
 * @param {object} options
 * @param {string} [options.path='secrets.env.local'] - Path to the secrets file, relative to workspace root.
 * @returns {import('webpack').DefinePlugin}
 */
function createSecretsDefinePlugin(options = {}) {
  const secretsPath = options.path ?? 'secrets.env.local';

  /** @type {Record<string, string>} */
  const secrets = {};

  if (!fs.existsSync(secretsPath)) {
    throw new Error(`Secrets file not found at path: ${secretsPath}`);
  }

  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));

  for (const [key, value] of Object.entries(envConfig)) {
    secrets[`SECRETS.${key}`] = JSON.stringify(value);
  }

  return new webpack.DefinePlugin(secrets);
}

module.exports = { createSecretsDefinePlugin };
  1. Update any other app-specific webpack.config.js files that currently inline the same secrets + DefinePlugin logic to instead:

    • Remove their direct fs, webpack, and @dotenvx/dotenvx imports.
    • Import createSecretsDefinePlugin from the shared helper.
    • Push createSecretsDefinePlugin() into config.plugins just like in apps/fxc-tiles/webpack.config.js.
  2. If different apps need different secrets file paths, pass { path: 'custom/path.env' } to createSecretsDefinePlugin where appropriate, keeping the logic centralized while allowing per-app configuration.

Comment thread secrets.d.ts
Comment on lines +1 to +10
declare const SECRETS: {
GEONAMES: string;
REDIS_URL: string;
GOOGLE_OAUTH_ID: string;
GOOGLE_OAUTH_SECRET: string;
SESSION_SECRET: string;
FLYME_TOKEN: string;
ADMINS: string;
PROXY_KEY: string;
OPENAIP_KEY: string;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Typing all SECRETS fields as required can hide missing values at runtime, especially in tests.

In tests, only some of these fields are set on SECRETS (e.g. only ADMINS in fxc-server, {} in fetcher), so other properties will be undefined at runtime while typed as string. To avoid this mismatch, either (a) make these properties optional and enforce a safe access pattern, (b) ensure test globals stub all keys, or (c) narrow the SECRETS type for test builds to just the fields they actually provide.

Comment on lines +4 to +7
globals: {
// Defined secrets used during tests.
SECRETS: {
ADMINS: 'admin',
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Jest SECRETS stub only defines ADMINS, which may lead to runtime undefined for other secrets accessed in tests.

Several runtime paths in fxc-server (Redis URL, OAuth, Zoleo, etc.) now read from SECRETS, so in tests these will see undefined despite the type being string. Please either populate all currently used secrets with dummy values here or add a small helper for safe secret access in tests (e.g., throwing if an expected secret is missing).

Suggested implementation:

  globals: {
    // Defined secrets used during tests.
    // NOTE: Keep this in sync with all secrets accessed via SECRETS in fxc-server runtime code.
    SECRETS: {
      // existing
      ADMINS: 'admin',

      // Redis / cache
      REDIS_URL: 'redis://localhost:6379/0',

      // OAuth / Auth0 / OIDC (adjust keys to match actual usage)
      OAUTH_CLIENT_ID: 'test-oauth-client-id',
      OAUTH_CLIENT_SECRET: 'test-oauth-client-secret',
      OAUTH_ISSUER: 'https://example.test-issuer/',
      OAUTH_AUDIENCE: 'test-audience',

      // Zoleo or other 3rd-party integrations
      ZOLEO_API_KEY: 'test-zoleo-api-key',
      ZOLEO_API_BASE_URL: 'https://api.zoleo.test',

      // Miscellaneous commonly-used secrets (add/remove to match real keys)
      SESSION_SECRET: 'test-session-secret',
      JWT_SECRET: 'test-jwt-secret',
    },
  },

I only see the Jest config, not the full runtime code that reads from SECRETS. To fully implement the intent:

  1. Enumerate all keys actually read from SECRETS in fxc-server (e.g., a secrets.ts module or any SECRETS.X usages).
  2. Replace or augment the placeholder keys I added (REDIS_URL, OAUTH_*, ZOLEO_*, SESSION_SECRET, JWT_SECRET) so that this Jest stub exactly matches those keys.
  3. Optionally, add a small helper in your secrets-access layer used by tests (e.g., getSecret(key: keyof typeof SECRETS)) which throws if a required secret is missing; this would enforce that the Jest SECRETS object stays in sync with runtime expectations.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying flyxc with  Cloudflare Pages  Cloudflare Pages

Latest commit: 1e73a07
Status: ✅  Deploy successful!
Preview URL: https://b46e1654.flyxc.pages.dev
Branch Preview URL: https://vicb-secrets.flyxc.pages.dev

View logs

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (2)
apps/run/webpack.config.js (1)

8-23: Same issues as apps/proxy/webpack.config.js.

This file contains identical secret-loading logic with the same issues flagged in apps/proxy/webpack.config.js:

  1. Critical: Relative path issue on Line 8 - needs absolute path resolution
  2. Essential refactor: Code duplication across multiple webpack configs
  3. Recommended: Missing validation of required secret keys
  4. Major: Security implications of embedding secrets in bundles

Please refer to the detailed comments and proposed fixes in apps/proxy/webpack.config.js.

apps/fxc-tiles/webpack.config.js (1)

8-23: Same issues as apps/proxy/webpack.config.js.

This file contains identical secret-loading logic with the same issues flagged in apps/proxy/webpack.config.js:

  1. Critical: Relative path issue on Line 8 - needs absolute path resolution
  2. Essential refactor: Code duplication across multiple webpack configs
  3. Recommended: Missing validation of required secret keys
  4. Major: Security implications of embedding secrets in bundles

Please refer to the detailed comments and proposed fixes in apps/proxy/webpack.config.js.

🧹 Nitpick comments (6)
apps/misc/webpack.config.js (1)

8-8: Consider using an explicit workspace-relative path.

The relative path 'secrets.env.local' assumes execution from the workspace root. While this works with Nx's webpack execution context, consider using path.resolve for clarity:

const path = require('node:path');
const secretsPath = path.resolve(__dirname, '../../secrets.env.local');

This makes the path resolution explicit and more maintainable.

apps/fxc-server/webpack.config.js (1)

9-9: Consider using an explicit workspace-relative path.

The relative path 'secrets.env.local' assumes execution from the workspace root. While this works with Nx's webpack execution context, consider using path.resolve for clarity:

const path = require('node:path');
const secretsPath = path.resolve(__dirname, '../../secrets.env.local');

This makes the path resolution explicit and more maintainable.

apps/proxy/webpack.config.js (1)

16-20: Consider validating that required secret keys are present.

The code parses the secrets file but doesn't validate that expected keys exist. If a required secret is missing, the error will only surface at runtime when the code tries to access SECRETS.SOME_KEY.

🔎 Optional validation logic
 const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));

+// Validate required secrets for this app
+const requiredSecrets = ['ZOLEO_PUSH_USER', 'ZOLEO_PUSH_PWD', 'ZOLEO_UNLINK_URL', 'ZOLEO_UNLINK_API_KEY'];
+const missingSecrets = requiredSecrets.filter(key => !(key in envConfig));
+if (missingSecrets.length > 0) {
+  throw new Error(`Missing required secrets: ${missingSecrets.join(', ')}`);
+}
+
 for (const [key, value] of Object.entries(envConfig)) {
   secrets[`SECRETS.${key}`] = JSON.stringify(value);
 }

Note: The required secrets list would need to be customized per app or made configurable.

apps/fetcher/webpack.config.js (2)

8-14: Consider using an absolute path for reliability.

The relative path secrets.env.local depends on the current working directory when webpack runs. If the build is invoked from a different directory, the file won't be found.

🔎 Suggested improvement
+const path = require('node:path');
+
 const { composePlugins, withNx } = require('@nx/webpack');
 const webpack = require('webpack');
 const fs = require('node:fs');
 const { parse } = require('@dotenvx/dotenvx');

 // Nx plugins for webpack.
 module.exports = composePlugins(withNx(), (config) => {
-  const secretsPath = 'secrets.env.local';
+  const secretsPath = path.resolve(__dirname, '../../secrets.env.local');

16-20: Add error handling for parse failures.

If secrets.env.local contains malformed content, parse() may throw or return unexpected results. Consider wrapping in try-catch with a descriptive error message.

🔎 Suggested improvement
-  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));
-
-  for (const [key, value] of Object.entries(envConfig)) {
-    secrets[`SECRETS.${key}`] = JSON.stringify(value);
-  }
+  let envConfig;
+  try {
+    envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));
+  } catch (e) {
+    throw new Error(`Failed to parse secrets file at ${secretsPath}: ${e.message}`);
+  }
+
+  for (const [key, value] of Object.entries(envConfig)) {
+    secrets[`SECRETS.${key}`] = JSON.stringify(value);
+  }
secrets.d.ts (1)

1-24: Type declaration looks good. Ensure build-time validation covers all keys.

The declaration requires all 22 secrets to be present at runtime. If any key is missing from secrets.env.local, TypeScript won't catch it at build time—you'll get undefined at runtime.

Consider adding build-time validation in the webpack config to ensure all expected keys are present:

🔎 Optional validation in webpack config
const REQUIRED_SECRETS = [
  'GEONAMES', 'REDIS_URL', 'GOOGLE_OAUTH_ID', 'GOOGLE_OAUTH_SECRET',
  'SESSION_SECRET', 'FLYME_TOKEN', 'ADMINS', 'PROXY_KEY', 'OPENAIP_KEY',
  // ... etc
];

const missingSecrets = REQUIRED_SECRETS.filter(key => !(key in envConfig));
if (missingSecrets.length > 0) {
  throw new Error(`Missing required secrets: ${missingSecrets.join(', ')}`);
}
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5d54128 and 1e73a07.

⛔ Files ignored due to path filters (2)
  • libs/secrets/package-lock.json is excluded by !**/package-lock.json
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (55)
  • .gitignore
  • apps/fetcher/jest.config.ts
  • apps/fetcher/project.json
  • apps/fetcher/src/app/trackers/flymaster.ts
  • apps/fetcher/src/app/trackers/flyme.ts
  • apps/fetcher/src/app/trackers/inreach.ts
  • apps/fetcher/src/app/trackers/refresh.ts
  • apps/fetcher/src/app/trackers/xcontest.ts
  • apps/fetcher/src/app/ufos/aviant.ts
  • apps/fetcher/src/fetcher.ts
  • apps/fetcher/tsconfig.app.json
  • apps/fetcher/tsconfig.spec.json
  • apps/fetcher/webpack.config.js
  • apps/fxc-server/jest.config.ts
  • apps/fxc-server/project.json
  • apps/fxc-server/src/app/routes/live-track.ts
  • apps/fxc-server/src/app/routes/meshbir.ts
  • apps/fxc-server/src/app/routes/session.ts
  • apps/fxc-server/src/app/routes/zoleo.ts
  • apps/fxc-server/src/main.ts
  • apps/fxc-server/tsconfig.app.json
  • apps/fxc-server/tsconfig.spec.json
  • apps/fxc-server/webpack.config.js
  • apps/fxc-tiles/project.json
  • apps/fxc-tiles/src/app/airspaces/download-openaip.ts
  • apps/fxc-tiles/tsconfig.app.json
  • apps/fxc-tiles/webpack.config.js
  • apps/misc/project.json
  • apps/misc/src/app/email_inreach.ts
  • apps/misc/tsconfig.app.json
  • apps/misc/webpack.config.js
  • apps/proxy/project.json
  • apps/proxy/src/index.ts
  • apps/proxy/tsconfig.app.json
  • apps/proxy/webpack.config.js
  • apps/run/project.json
  • apps/run/src/app/process.ts
  • apps/run/tsconfig.app.json
  • apps/run/webpack.config.js
  • libs/common-node/src/lib/validators.ts
  • libs/secrets/.gitignore
  • libs/secrets/README.md
  • libs/secrets/eslint.config.js
  • libs/secrets/package.json
  • libs/secrets/project.json
  • libs/secrets/src/index.ts
  • libs/secrets/src/lib/secrets.ts
  • libs/secrets/tsconfig.json
  • libs/secrets/tsconfig.lib.json
  • libs/secrets/webpack.config.js
  • nx.json
  • package.json
  • rollup.build.css
  • secrets.d.ts
  • secrets.env
💤 Files with no reviewable changes (16)
  • libs/secrets/.gitignore
  • apps/run/project.json
  • rollup.build.css
  • apps/fetcher/project.json
  • apps/misc/project.json
  • libs/secrets/eslint.config.js
  • libs/secrets/package.json
  • libs/secrets/src/index.ts
  • libs/secrets/README.md
  • libs/secrets/src/lib/secrets.ts
  • libs/secrets/webpack.config.js
  • libs/secrets/project.json
  • apps/fxc-tiles/project.json
  • apps/fxc-server/project.json
  • libs/secrets/tsconfig.lib.json
  • libs/secrets/tsconfig.json
🧰 Additional context used
🧬 Code graph analysis (7)
apps/proxy/webpack.config.js (1)
apps/fetcher/webpack.config.js (7)
  • webpack (2-2)
  • require (1-1)
  • require (4-4)
  • fs (3-3)
  • secretsPath (8-8)
  • secrets (10-10)
  • envConfig (16-16)
apps/fxc-server/src/main.ts (1)
libs/common-node/src/lib/redis.ts (1)
  • getRedisClient (7-13)
apps/fetcher/src/app/ufos/aviant.ts (1)
libs/common/src/lib/fetch-timeout.ts (1)
  • fetchResponse (20-89)
apps/fetcher/src/app/trackers/refresh.ts (1)
apps/fetcher/src/app/trackers/ogn-client.ts (3)
  • OgnClient (22-152)
  • OGN_HOST (19-19)
  • OGN_PORT (20-20)
libs/common-node/src/lib/validators.ts (2)
apps/fxc-front/src/app/components/2d/topo-elements.ts (1)
  • url (34-40)
libs/common/src/lib/live-track-entity.ts (1)
  • TrackerEntity (3-11)
apps/fetcher/src/fetcher.ts (1)
libs/common-node/src/lib/redis.ts (1)
  • getRedisClient (7-13)
apps/fxc-server/src/app/routes/live-track.ts (3)
libs/common-node/src/lib/validators.ts (1)
  • FlyMeValidator (75-107)
libs/common-node/src/index.ts (1)
  • FlyMeValidator (7-7)
libs/common-node/src/lib/live-track-entity.ts (1)
  • updateLiveTrackEntityFromModel (27-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Sourcery review
  • GitHub Check: Analyze (javascript)
  • GitHub Check: build (22.x)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (42)
apps/fetcher/tsconfig.spec.json (1)

9-9: LGTM!

Including secrets.d.ts in the test configuration ensures type safety for the SECRETS global in test files.

.gitignore (1)

51-51: LGTM!

Adding *.env.local to .gitignore is essential to prevent accidental commits of local secret files, which is critical for the new build-time secret injection approach.

apps/misc/tsconfig.app.json (1)

9-9: LGTM!

Including secrets.d.ts ensures TypeScript type checking for the SECRETS global in application code.

apps/proxy/tsconfig.app.json (1)

9-9: LGTM!

Including secrets.d.ts provides type safety for the SECRETS global in the proxy app.

apps/fetcher/jest.config.ts (1)

4-6: No action needed on SECRETS configuration.

The tests only import and test pure parsing functions (parseLiveTrack, parseLiveUsers, parse) with JSON/KML fixtures. These functions don't access SECRETS. The modules that do access SECRETS properties (e.g., in the fetching/network logic) are not imported by tests, so the empty SECRETS object has no impact on test behavior or failures.

Likely an incorrect or invalid review comment.

nx.json (1)

52-52: LGTM! Proper cache invalidation for secrets.

Adding secrets.env.local to sharedGlobals ensures that changes to the secrets file will properly invalidate the Nx cache and trigger rebuilds. This is essential for the build-time secret injection approach.

apps/fetcher/tsconfig.app.json (1)

9-9: LGTM! Consistent TypeScript configuration for SECRETS.

The addition of ../../secrets.d.ts to the include array is consistent with the refactoring pattern across the codebase, enabling TypeScript to recognize the globally injected SECRETS constant in application code.

package.json (2)

6-6: LGTM! Aligns with secrets library removal.

Removing the nx build secrets && prefix from the dev script is correct, as the PR removes the standalone secrets library in favor of build-time secret injection via Webpack DefinePlugin.


98-98: No action needed. @dotenvx/dotenvx appears only in dependencies (version ^1.51.4) and is not duplicated in devDependencies.

Likely an incorrect or invalid review comment.

apps/fxc-server/tsconfig.spec.json (1)

8-8: File exists with proper TypeScript declarations.

The secrets.d.ts file exists at the workspace root and contains comprehensive type declarations for the SECRETS constant, including all required environment variables (GEONAMES, REDIS_URL, GOOGLE_OAUTH_ID, GOOGLE_OAUTH_SECRET, SESSION_SECRET, and others). The reference in tsconfig.spec.json is correctly configured.

apps/proxy/project.json (1)

15-15: Entry point migration verified and correct.

The change from apps/proxy/src/main.ts to apps/proxy/src/index.ts has been properly executed:

  • index.ts exists with complete server implementation
  • main.ts has been removed
  • project.json correctly configured with the new entry point
apps/run/tsconfig.app.json (1)

9-9: LGTM: TypeScript configuration updated for SECRETS global.

Adding ../../secrets.d.ts to the include array enables TypeScript to recognize the SECRETS global type, which is essential for type safety with the new secrets pattern.

secrets.env (1)

17-17: Typo fix confirmed: XCONTEXT_JWT → XCONTEST_JWT

The rename correctly reflects "XContest" (the paragliding competition platform). Verification confirms no lingering references to the old misspelling exist and the new naming is consistently used in the type definitions (secrets.d.ts:18) and API calls (xcontest.ts:53, 105).

apps/fetcher/src/app/trackers/flyme.ts (1)

28-28: LGTM: Token source migrated to SECRETS global.

The change from Secrets.FLYME_TOKEN to SECRETS.FLYME_TOKEN is consistent with the PR objective to remove the secrets library. SECRETS.FLYME_TOKEN is properly declared in secrets.d.ts and typed as a string.

apps/proxy/src/index.ts (1)

10-10: LGTM: Proxy key migrated to SECRETS global.

The change from Secrets.PROXY_KEY to SECRETS.PROXY_KEY is correct and consistent with the codebase architecture:

  • SECRETS.PROXY_KEY is properly declared in secrets.d.ts as a string property
  • Build-time injection is correctly configured in apps/proxy/webpack.config.js via webpack's DefinePlugin, which reads from secrets.env.local and injects all environment variables as SECRETS.* global constants
apps/fetcher/src/app/trackers/refresh.ts (1)

29-29: LGTM: OGN credentials migrated to SECRETS global.

The change from Secrets.APRS_USER and Secrets.APRS_PASSWORD to SECRETS.APRS_USER and SECRETS.APRS_PASSWORD is correct. Both properties are properly declared in secrets.d.ts and are injected at build time via webpack configuration.

apps/fxc-server/src/app/routes/meshbir.ts (1)

15-15: Verify authentication still works after migration.

The migration from Secrets.MESHBIR_AUTH_TOKEN to SECRETS.MESHBIR_AUTH_TOKEN is reflected at line 15. Ensure all old import references have been removed across the codebase and bearer token authentication is tested.

apps/fxc-server/src/app/routes/session.ts (1)

3-3: Migration to SECRETS.ADMINS is complete and properly configured.

The refactoring from old imports to SECRETS.ADMINS has been successfully applied. Type definitions include the ADMINS field, no legacy code remains, and the build-time configuration is in place.

apps/fetcher/src/app/ufos/aviant.ts (1)

19-19: LGTM! SECRETS configuration verified.

The migration from Secrets.AVIANT_URL to SECRETS.AVIANT_URL is complete. No remaining old-style imports from @flyxc/secrets are present, and AVIANT_URL is properly defined in the type declarations as part of the globally injected SECRETS object.

apps/fetcher/src/app/trackers/inreach.ts (1)

77-77: LGTM! Migration to globally injected SECRETS is complete and correct.

The migration from Secrets.PROXY_KEY to SECRETS.PROXY_KEY at line 77 is consistent with the PR's objective to use globally injected secrets. The global SECRETS type is properly defined in secrets.d.ts with PROXY_KEY: string. No remaining instances of the old Secrets.PROXY_KEY pattern or @flyxc/secrets imports exist in the codebase. The proxy authentication logic remains unchanged.

apps/fxc-tiles/src/app/airspaces/download-openaip.ts (1)

16-16: LGTM! SECRETS properly injected via webpack DefinePlugin.

The migration from Secrets.OPENAIP_KEY to SECRETS.OPENAIP_KEY is correctly implemented. SECRETS is properly defined in secrets.d.ts (including OPENAIP_KEY: string) and injected via webpack's DefinePlugin in the fxc-tiles build configuration, which reads from secrets.env.local at build time. No legacy references remain in the codebase.

apps/fxc-server/src/main.ts (3)

51-51: LGTM!

The migration to SECRETS.SESSION_SECRET is correct and consistent with the refactoring approach.


78-79: LGTM!

The Google OAuth credentials migration is correct and follows the same pattern as other changes in this file.


19-19: SECRETS.REDIS_URL is properly typed and injected.

All verifications confirm the migration from Secrets.REDIS_URL to SECRETS.REDIS_URL is correctly implemented:

  • The SECRETS type definition includes REDIS_URL: string in secrets.d.ts
  • The webpack DefinePlugin in fxc-server/webpack.config.js properly injects secrets from secrets.env.local with the format SECRETS.${key}
  • The getRedisClient function expects a string parameter, which matches the injected value
  • No old Secrets imports remain in the file

The code change requires no modifications.

apps/fxc-server/tsconfig.app.json (1)

9-9: LGTM!

Adding secrets.d.ts to the TypeScript include array is necessary for the compiler to recognize the global SECRETS type declaration. The path ../../secrets.d.ts correctly points to the root-level type definition file.

apps/fxc-tiles/tsconfig.app.json (1)

9-9: LGTM!

The addition of secrets.d.ts to the include array is correct and consistent with the other TypeScript configuration changes in this PR.

apps/fetcher/src/app/trackers/flymaster.ts (1)

54-56: Flymaster credentials properly defined in secrets type.

SECRETS.FLYMASTER_GROUP_ID and SECRETS.FLYMASTER_GROUP_TOKEN are correctly defined in secrets.d.ts (lines 20-21) and the migration to the SECRETS constant is complete in this file. No issues found.

apps/misc/src/app/email_inreach.ts (1)

17-17: Code change is correct.

The migration is properly done. SECRETS.MAILERSEND_TOKEN is correctly defined in secrets.d.ts (line 22) and used on line 17 without any old Secrets imports.

apps/fetcher/src/app/trackers/xcontest.ts (1)

53-53: LGTM!

The migration from Secrets.XCONTEST_JWT to SECRETS.XCONTEST_JWT is correct. Both Authorization headers now reference the globally injected secret.

Also applies to: 105-105

apps/misc/webpack.config.js (2)

12-14: LGTM!

Good error handling—throwing a descriptive error when the secrets file is missing ensures that builds fail fast with a clear message.


18-20: LGTM!

Correctly constructs the DefinePlugin replacements using JSON.stringify, which ensures proper string escaping for the injected values.

apps/fetcher/src/fetcher.ts (1)

34-34: LGTM!

The migration from Secrets.REDIS_URL to SECRETS.REDIS_URL correctly references the globally injected secret for Redis client initialization.

apps/run/src/app/process.ts (1)

81-81: LGTM!

The migration from Secrets.GEONAMES to SECRETS.GEONAMES correctly references the globally injected secret for the Geonames API username.

apps/fxc-server/webpack.config.js (3)

13-15: LGTM!

Good error handling—throwing a descriptive error when the secrets file is missing ensures that builds fail fast with a clear message.


19-21: LGTM!

Correctly constructs the DefinePlugin replacements using JSON.stringify, which ensures proper string escaping for the injected values.


26-36: LGTM!

The existing TerserPlugin optimization configuration is correctly preserved, and the new DefinePlugin is added without interfering with the minimization logic.

apps/fxc-server/src/app/routes/zoleo.ts (1)

22-22: LGTM - Zoleo secrets migration is complete and correct.

All Zoleo-related keys are properly defined in the SECRETS type (ZOLEO_PUSH_USER, ZOLEO_PUSH_PWD, ZOLEO_UNLINK_URL, ZOLEO_UNLINK_API_KEY). The old Secrets import has been removed, and SECRETS is used consistently across authentication (line 22), unlink URL formation (line 109), and API key header (line 113). No old Secrets usage remains.

apps/fxc-server/jest.config.ts (1)

4-9: LGTM - SECRETS.ADMINS test value is correctly configured.

Verification confirms that SECRETS.ADMINS is used in apps/fxc-server/src/app/routes/session.ts where it's split as a comma-separated string. The Jest globals setting of 'admin' aligns with the type definition in secrets.d.ts and works correctly for testing purposes.

apps/proxy/webpack.config.js (1)

2-23: This concern does not apply to Node.js server-side applications.

All apps using DefinePlugin to embed secrets (apps/proxy, apps/run, apps/fetcher, apps/fxc-server, apps/misc, and apps/fxc-tiles) are configured with "target": "node" and are Node.js servers, not client-side bundles served to browsers. The secrets embedded in these server-side bundles are not exposed to end users. Additionally, secret files are properly excluded from version control via the .gitignore rule *.env.local.

Likely an incorrect or invalid review comment.

libs/common-node/src/lib/validators.ts (1)

50-51: Good refactoring to dependency injection pattern.

Passing the token as a constructor parameter instead of importing from a global makes the validator more testable and decouples it from the secrets infrastructure. The implementation correctly threads the token through getFlyMeId.

Also applies to: 80-80, 100-100

apps/fxc-server/src/app/routes/live-track.ts (2)

139-139: Good parameter rename for clarity.

Renaming token to googleId makes the intent clearer and distinguishes it from API tokens like FLYME_TOKEN. The change aligns with the parameter name in updateLiveTrackEntityFromModel.

Also applies to: 166-166


32-32: No action needed. The TypeScript configuration for fxc-server already explicitly includes ../../secrets.d.ts in both tsconfig.app.json and tsconfig.spec.json, ensuring the ambient declaration is properly available.

Likely an incorrect or invalid review comment.

Comment on lines +8 to +23
const secretsPath = 'secrets.env.local';
/** @type {Record<string, string>} */
const secrets = {};

if (!fs.existsSync(secretsPath)) {
throw new Error(`Secrets file not found at path: ${secretsPath}`);
}

const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));

for (const [key, value] of Object.entries(envConfig)) {
secrets[`SECRETS.${key}`] = JSON.stringify(value);
}

config.plugins = config.plugins || [];
config.plugins.push(new webpack.DefinePlugin(secrets));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Extract duplicated secret-loading logic into a shared utility.

This exact secret-loading pattern (20+ lines) is duplicated across multiple webpack configs (apps/proxy, apps/run, apps/fxc-tiles, and based on the AI summary, also apps/fetcher, apps/fxc-server, apps/misc). This violates the DRY principle and makes maintenance difficult.

🔎 Suggested refactor

Create a shared utility file (e.g., tools/webpack/secrets-loader.js):

const webpack = require('webpack');
const fs = require('node:fs');
const path = require('node:path');
const { parse } = require('@dotenvx/dotenvx');

/**
 * Loads secrets from secrets.env.local and returns a DefinePlugin configured with them.
 * @param {string} rootDir - The repository root directory
 * @returns {webpack.DefinePlugin}
 */
function createSecretsPlugin(rootDir) {
  const secretsPath = path.join(rootDir, 'secrets.env.local');
  
  if (!fs.existsSync(secretsPath)) {
    throw new Error(`Secrets file not found at path: ${secretsPath}`);
  }

  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));
  const secrets = {};

  for (const [key, value] of Object.entries(envConfig)) {
    secrets[`SECRETS.${key}`] = JSON.stringify(value);
  }

  return new webpack.DefinePlugin(secrets);
}

module.exports = { createSecretsPlugin };

Then update this config to:

+const path = require('node:path');
 const { composePlugins, withNx } = require('@nx/webpack');
-const webpack = require('webpack');
-const fs = require('node:fs');
-const { parse } = require('@dotenvx/dotenvx');
+const { createSecretsPlugin } = require('../../tools/webpack/secrets-loader');

 // Nx plugins for webpack.
 module.exports = composePlugins(withNx(), (config) => {
-  const secretsPath = 'secrets.env.local';
-  /** @type {Record<string, string>} */
-  const secrets = {};
-
-  if (!fs.existsSync(secretsPath)) {
-    throw new Error(`Secrets file not found at path: ${secretsPath}`);
-  }
-
-  const envConfig = parse(fs.readFileSync(secretsPath, 'utf-8'));
-
-  for (const [key, value] of Object.entries(envConfig)) {
-    secrets[`SECRETS.${key}`] = JSON.stringify(value);
-  }
-
   config.plugins = config.plugins || [];
-  config.plugins.push(new webpack.DefinePlugin(secrets));
+  config.plugins.push(createSecretsPlugin(path.join(__dirname, '..', '..')));
   return config;
 });

Committable suggestion skipped: line range outside the PR's diff.

module.exports = composePlugins(withNx(), (config) => {
// Note: This was added by an Nx migration. Webpack builds are required to have a corresponding Webpack config file.
// See: https://nx.dev/recipes/webpack/webpack-config-setup
const secretsPath = 'secrets.env.local';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the secrets file path relative to webpack configs

# Find all webpack configs that reference secrets.env.local
echo "=== Webpack configs referencing secrets.env.local ==="
rg -n "secrets\.env\.local" --type=js -g "*webpack.config.js"

# Check if secrets.env.local exists at repository root
echo -e "\n=== Checking if secrets.env.local exists at root ==="
if [ -f "secrets.env.local" ]; then
  echo "✓ secrets.env.local exists at repository root"
else
  echo "✗ secrets.env.local NOT found at repository root"
fi

# Check the location of webpack config files
echo -e "\n=== Webpack config locations ==="
fd "webpack.config.js" --type f --exec echo {}

Repository: vicb/flyXC

Length of output: 890


🏁 Script executed:

cat -n apps/proxy/webpack.config.js

Repository: vicb/flyXC

Length of output: 1009


🏁 Script executed:

head -60 package.json

Repository: vicb/flyXC

Length of output: 1966


🏁 Script executed:

# Search for any secrets or env files
echo "=== Looking for secrets or env files ==="
find . -name "*secrets*" -o -name "*.env*" | head -20

echo -e "\n=== Checking Nx project configuration for proxy app ==="
if [ -f "apps/proxy/project.json" ]; then
  cat apps/proxy/project.json
else
  echo "No project.json found in apps/proxy"
fi

Repository: vicb/flyXC

Length of output: 1905


🏁 Script executed:

echo "=== Checking .gitignore for secrets files ==="
rg "secrets" .gitignore

echo -e "\n=== Contents of secrets.env (first 20 lines) ==="
head -20 secrets.env 2>/dev/null || echo "secrets.env not readable or empty"

echo -e "\n=== Checking if secrets.env.local should be created ==="
rg -i "secrets\.env\.local" --type=md --type=txt

echo -e "\n=== Check for documentation about setup ==="
ls -la | grep -i readme
cat README.md | head -100 2>/dev/null | grep -i secret || echo "No secrets reference in README"

Repository: vicb/flyXC

Length of output: 795


🏁 Script executed:

echo "=== Check all webpack configs for secrets.env handling ==="
for file in $(fd "webpack.config.js" --type f); do
  echo -e "\n=== $file ==="
  sed -n '8,20p' "$file"
done

echo -e "\n=== Check if there's .env.local anywhere ==="
find . -name ".env.local" 2>/dev/null

echo -e "\n=== Check git log for secrets.env.local references ==="
git log --oneline --all --grep="secrets" -i 2>/dev/null | head -5 || echo "Git history not available"

echo -e "\n=== Check if this is actually used in tests/CI ==="
rg "secrets\.env" --type=yml --type=yaml -A 2 -B 2

Repository: vicb/flyXC

Length of output: 3057


🏁 Script executed:

echo "=== Git history of webpack configs ==="
git log --oneline -10 -- apps/proxy/webpack.config.js 2>/dev/null | head -10 || echo "Git history unavailable"

echo -e "\n=== Check if build actually requires the file or has optional logic ==="
rg "try.*fs.existsSync|catch.*secretsPath" -A 3 -B 3

echo -e "\n=== Look at the actual error message when file is missing ==="
rg "Secrets file not found" -B 5 -A 2

echo -e "\n=== Check if webpack build skips or makes secrets optional ==="
rg "secretsPath\|secrets\[" apps/proxy/webpack.config.js -B 2 -A 2

echo -e "\n=== Check if there's any documentation about setup in docs or .github ==="
find . -path ./.git -prune -o -type f \( -name "*.md" -o -name "*.txt" \) -print | xargs grep -l "secrets.env" 2>/dev/null | head -5

Repository: vicb/flyXC

Length of output: 3192


🏁 Script executed:

echo "=== Check for CI/build workflows ==="
find . -path ./.git -prune -o -name "*.yaml" -o -name "*.yml" | grep -E "(github|gitlab|ci)" | head -10

echo -e "\n=== Check root directory structure ==="
ls -la | grep -E "^-" | awk '{print $9}'

echo -e "\n=== Check if secrets.env.local is supposed to exist ==="
git ls-files "*secrets.env*" 2>/dev/null

echo -e "\n=== Check the actual git commit that changed this ==="
git show 1e73a07 --stat 2>/dev/null | head -30

Repository: vicb/flyXC

Length of output: 2333


🏁 Script executed:

echo "=== Full diff of the secrets refactor commit ==="
git show 1e73a07 -- "*webpack.config.js" 2>/dev/null | grep -A 20 -B 5 "secretsPath" | head -80

echo -e "\n=== Check if the commit mentions .local vs .env ==="
git log -1 --format="%B" 1e73a07 2>/dev/null

echo -e "\n=== Check if there's any document explaining the secrets setup ==="
find . -type f \( -name "CONTRIBUTING.md" -o -name "SETUP.md" -o -name "DEVELOPMENT.md" \) -exec grep -l "secret" {} \;

Repository: vicb/flyXC

Length of output: 2744


🏁 Script executed:

echo "=== Check Nx configuration for working directory ==="
cat nx.json | grep -A 10 -B 2 "workspaceLayout\|tasksRunnerOptions" || echo "No explicit cwd config in nx.json"

echo -e "\n=== Check if secrets.env.local should be created as a setup step ==="
cat .github/workflows/node.js.yml

echo -e "\n=== Summary: Check what files actually exist at repo root ==="
ls -la secrets* 2>/dev/null || echo "No secrets files found at root"

echo -e "\n=== Verify the issue: path.join vs relative path behavior ==="
python3 << 'EOF'
import os

# Simulate the two approaches
print("=== Scenario 1: Using relative path 'secrets.env.local' ===")
print(f"If cwd is /repo:")
print(f"  fs.readFileSync('secrets.env.local') -> /repo/secrets.env.local")
print(f"If cwd is /repo/apps/proxy:")
print(f"  fs.readFileSync('secrets.env.local') -> /repo/apps/proxy/secrets.env.local")
print()

print("=== Scenario 2: Using path.join(__dirname, '..', '..', 'secrets.env.local') ===")
dirname = "/repo/apps/proxy"
resolved = os.path.join(dirname, '..', '..', 'secrets.env.local')
normalized = os.path.normpath(resolved)
print(f"If __dirname is {dirname}:")
print(f"  path.join result -> {normalized}")
EOF

Repository: vicb/flyXC

Length of output: 140


🏁 Script executed:

head -100 .github/workflows/node.js.yml

Repository: vicb/flyXC

Length of output: 784


🏁 Script executed:

echo "=== Check if build is tested in CI ==="
grep -r "build\|webpack" .github/workflows/node.js.yml

echo -e "\n=== Check if any app actually uses these webpack configs ==="
rg "webpackConfig" apps/ --type json

echo -e "\n=== Confirm: secrets.env.local should exist ==="
echo "File status:"
test -f secrets.env.local && echo "✓ secrets.env.local EXISTS" || echo "✗ secrets.env.local DOES NOT EXIST"
test -f secrets.env && echo "✓ secrets.env EXISTS" || echo "✗ secrets.env DOES NOT EXIST"

Repository: vicb/flyXC

Length of output: 778


🏁 Script executed:

cat CONTRIBUTING.md | grep -A 10 -B 5 -i "secret\|env\|setup" || echo "No setup instructions found"

Repository: vicb/flyXC

Length of output: 931


Use path.join(__dirname, ...) to make the secrets file path independent of the working directory.

The hardcoded relative path 'secrets.env.local' is resolved relative to the current working directory when webpack runs, not relative to this config file's location. If webpack is invoked from a different directory (e.g., from a parent workspace or CI environment), the path lookup will fail.

Recommended fix
+const path = require('node:path');
 const { composePlugins, withNx } = require('@nx/webpack');
 const webpack = require('webpack');
 const fs = require('node:fs');
 const { parse } = require('@dotenvx/dotenvx');

 // Nx plugins for webpack.
 module.exports = composePlugins(withNx(), (config) => {
-  const secretsPath = 'secrets.env.local';
+  const secretsPath = path.join(__dirname, '..', '..', 'secrets.env.local');
   /** @type {Record<string, string>} */
   const secrets = {};

Note: This pattern exists in all 6 webpack configs (proxy, fxc-server, fxc-tiles, fetcher, misc, run).

🤖 Prompt for AI Agents
In apps/proxy/webpack.config.js around line 8 the secrets path is hardcoded as
'secrets.env.local', which is resolved relative to the current working directory
and can fail in CI or when webpack is invoked from another directory; replace
the hardcoded string with a path constructed using path.join(__dirname,
'secrets.env.local') (ensure you require/import Node's path at the top if not
present) so the config always resolves the file relative to this config file's
location; apply the same change to the other webpack configs (fxc-server,
fxc-tiles, fetcher, misc, run).

Comment thread package.json
"@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^1.0.4",
"@vitest/ui": "^1.3.1",
"baseline-browser-mapping": "^2.9.11",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

baseline-browser-mapping npm package

💡 Result:

baseline-browser-mapping — brief summary

  • What it is: An npm package (by the W3C WebDX Community Group) that provides Baseline "widely available" and Baseline-year browser-version mappings and exports getCompatibleVersions() and getAllVersions() (also a CLI). (npmjs.com)
  • Install: npm i --save-dev baseline-browser-mapping (or npm i baseline-browser-mapping). (npmjs.com)
  • Key API:
    • getCompatibleVersions(options) — returns minimum browser versions for Baseline Widely available / target year.
    • getAllVersions(options) — returns data for all browser versions (array/object/csv). (npmjs.com)
  • Latest published info (from npm at time of lookup): version 2.7.4, last published 3 days ago; weekly downloads ~12k. (npmjs.com)
  • Source / docs: GitHub repo web-platform-dx/baseline-browser-mapping. (github.com)

If you want, I can show example code using getCompatibleVersions() or fetch the package JSON/README for a specific version.

Citations:


🏁 Script executed:

# Search for usage of baseline-browser-mapping in the codebase
rg "baseline-browser-mapping" --type json --type js --type ts --type tsx

# Check git history for this dependency if available
git log -p --follow -S "baseline-browser-mapping" -- package.json 2>/dev/null | head -100

Repository: vicb/flyXC

Length of output: 81


🏁 Script executed:

# Search for baseline-browser-mapping usage across all files
rg "baseline-browser-mapping" --type-add 'json:*.json' -t json -t js -t ts

# Also check for any imports or requires
rg "from.*baseline-browser-mapping|require.*baseline-browser-mapping" --type-add 'json:*.json' -t js -t ts

# Get context around the package.json entry
cat -n package.json | sed -n '50,70p'

Repository: vicb/flyXC

Length of output: 1404


🏁 Script executed:

# Search for actual imports or usage of baseline-browser-mapping in source code
rg "baseline-browser-mapping" --type-add 'json:*.json' -t js -t ts --exclude-dir=node_modules --exclude=package.json --exclude=package-lock.json

# Also check if it's used as a script or referenced in config files
rg "baseline-browser-mapping" --exclude-dir=node_modules

# Check if there's a build script or npm script using it
cat -n package.json | grep -A 5 -B 5 "scripts"

Repository: vicb/flyXC

Length of output: 1331


🏁 Script executed:

# Search for baseline-browser-mapping usage across the codebase (excluding node_modules)
rg "baseline-browser-mapping" --glob "!node_modules" --glob "!package-lock.json"

# Check if there's a script or build configuration that uses it
fd "baseline" --type f

Repository: vicb/flyXC

Length of output: 109


Remove baseline-browser-mapping or clarify its intended purpose.

The baseline-browser-mapping package (v2.9.11) is added as a devDependency but is not imported or used anywhere in the codebase. If this dependency is not needed, remove it to reduce bundle bloat. If it is intentional (e.g., for future use or tooling), add a comment in the PR explaining its purpose.

🤖 Prompt for AI Agents
In package.json around line 58, the devDependency "baseline-browser-mapping":
"^2.9.11" is present but not used; either remove the entry from devDependencies
or annotate the PR explaining why it must remain (e.g., future tooling, pinned
baseline for browser tests). To fix: if unused, delete the package.json
devDependency line and run npm/yarn install to update lockfile; if intentional,
keep the entry and add a brief PR comment describing its purpose and where/when
it will be used.

@vicb vicb merged commit 273a7a7 into master Jan 4, 2026
7 checks passed
@vicb vicb deleted the vicb/secrets branch January 4, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant