mirror of
https://github.com/bitwarden/directory-connector
synced 2026-01-28 23:33:15 +00:00
Compare commits
7 Commits
renovate/j
...
context-ru
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22fe0bc482 | ||
|
|
5b1dd63c49 | ||
|
|
1af8fc1067 | ||
|
|
6c2f54bad5 | ||
|
|
bb9a6a61ee | ||
|
|
f0a19b6267 | ||
|
|
220d6c02c7 |
@@ -1,203 +1,706 @@
|
||||
# Bitwarden Directory Connector
|
||||
# Bitwarden Directory Connector - Claude Code Configuration
|
||||
|
||||
## Project Overview
|
||||
Sync users and groups from enterprise directory services (LDAP, Entra ID, Google Workspace, Okta, OneLogin) to Bitwarden organizations. Available as both a desktop GUI (Electron + Angular) and a CLI tool (`bwdc`).
|
||||
|
||||
Directory Connector is a TypeScript application that synchronizes users and groups from directory services to Bitwarden organizations. It provides both a desktop GUI (built with Angular and Electron) and a CLI tool (bwdc).
|
||||
## Overview
|
||||
|
||||
**Supported Directory Services:**
|
||||
### What This Project Does
|
||||
|
||||
- LDAP (Lightweight Directory Access Protocol) - includes Active Directory and general LDAP servers
|
||||
- Microsoft Entra ID (formerly Azure Active Directory)
|
||||
- Google Workspace
|
||||
- Okta
|
||||
- OneLogin
|
||||
- Connects to enterprise identity providers and retrieves user/group membership data
|
||||
- Syncs that data to Bitwarden organizations via the Directory Connector API
|
||||
- Provides both a desktop GUI application (Electron) and a command-line interface (`bwdc`)
|
||||
|
||||
**Technologies:**
|
||||
### Key Concepts
|
||||
|
||||
- TypeScript
|
||||
- Angular (GUI)
|
||||
- Electron (Desktop wrapper)
|
||||
- Node
|
||||
- Jest for testing
|
||||
- **Directory Service**: An identity provider (LDAP, Entra ID, GSuite, Okta, OneLogin) that stores users and groups
|
||||
- **Sync**: The process of fetching entries from a directory and importing them to Bitwarden
|
||||
- **Delta Sync**: Incremental synchronization that only fetches changes since the last sync
|
||||
- **Entry**: Base class for `UserEntry` and `GroupEntry` - the core data models
|
||||
- **Force Sync**: Ignores delta tokens and fetches all entries fresh
|
||||
- **Test Mode**: Simulates sync without making API calls or updating state
|
||||
|
||||
## Code Architecture & Structure
|
||||
---
|
||||
|
||||
### Directory Organization
|
||||
## Architecture & Patterns
|
||||
|
||||
### System Architecture
|
||||
|
||||
```
|
||||
src/
|
||||
├── abstractions/ # Interface definitions (e.g., IDirectoryService)
|
||||
├── services/ # Business logic implementations for directory services, sync, auth
|
||||
├── models/ # Data models (UserEntry, GroupEntry, etc.)
|
||||
├── commands/ # CLI command implementations
|
||||
├── app/ # Angular GUI components
|
||||
└── utils/ # Test utilities and fixtures
|
||||
|
||||
src-cli/ # CLI-specific code (imports common code from src/)
|
||||
|
||||
jslib/ # Legacy folder structure (mix of deprecated/unused and current code - new code should not be added here)
|
||||
User Request (GUI/CLI)
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ Entry Points │
|
||||
│ main.ts (GUI) │ bwdc.ts (CLI) │
|
||||
└───────────────────────────────────┘
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ SyncService │
|
||||
│ Orchestrates the sync flow │
|
||||
└───────────────────────────────────┘
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ DirectoryFactoryService │
|
||||
│ Creates appropriate IDirectory │
|
||||
└───────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ Directory Services │
|
||||
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────┐ │
|
||||
│ │ LDAP │ │ EntraID │ │ GSuite │ │ Okta/1Login │ │
|
||||
│ └─────────┘ └─────────┘ └─────────┘ └─────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ [GroupEntry[], UserEntry[]]│
|
||||
└───────────────────────────────────┘
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ RequestBuilder (Batched) │
|
||||
│ SingleRequestBuilder (<2000) │
|
||||
│ BatchRequestBuilder (>2000) │
|
||||
└───────────────────────────────────┘
|
||||
↓
|
||||
┌───────────────────────────────────┐
|
||||
│ Bitwarden API │
|
||||
│ POST /import endpoint │
|
||||
└───────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Key Architectural Patterns
|
||||
|
||||
1. **Abstractions = Interfaces**: All interfaces are defined in `/abstractions`
|
||||
2. **Services = Business Logic**: Implementations live in `/services`
|
||||
3. **Directory Service Pattern**: Each directory provider implements `IDirectoryService` interface
|
||||
4. **Separation of Concerns**: GUI (Angular app) and CLI (commands) share the same service layer
|
||||
|
||||
## Development Conventions
|
||||
|
||||
### Code Organization
|
||||
|
||||
**File Naming:**
|
||||
```
|
||||
src/
|
||||
├── abstractions/ # Interface definitions (IDirectoryService, etc.)
|
||||
├── app/ # Angular GUI components
|
||||
│ ├── tabs/ # Tab-based navigation (Dashboard, Settings, More)
|
||||
│ └── services/ # Angular service providers
|
||||
├── commands/ # CLI command implementations
|
||||
├── enums/ # TypeScript enums (DirectoryType, etc.)
|
||||
├── models/ # Data models (Entry, UserEntry, GroupEntry)
|
||||
├── services/ # Business logic implementations
|
||||
│ └── directory-services/ # One service per directory provider
|
||||
├── bwdc.ts # CLI entry point
|
||||
├── main.ts # Electron main process entry point
|
||||
└── program.ts # CLI command routing (Commander.js)
|
||||
|
||||
- kebab-case for files: `ldap-directory.service.ts`
|
||||
- Descriptive names that reflect purpose
|
||||
jslib/ # Legacy shared libraries (do not add new code here)
|
||||
utils/ # Integration test fixtures
|
||||
└── openldap/ # Docker configs, test data, certificates
|
||||
```
|
||||
|
||||
**Class/Function Naming:**
|
||||
### Key Principles
|
||||
|
||||
- PascalCase for classes and interfaces
|
||||
- camelCase for functions and variables
|
||||
- Descriptive names that indicate purpose
|
||||
1. **Shared Service Layer**: GUI (Angular) and CLI share identical service implementations
|
||||
2. **Factory Pattern**: `DirectoryFactoryService` instantiates the correct `IDirectoryService` based on `DirectoryType`
|
||||
3. **Secure Storage**: Credentials stored in system keychain via `KeytarSecureStorageService`
|
||||
4. **Delta Tracking**: Incremental sync via delta tokens to minimize API calls
|
||||
|
||||
**File Structure:**
|
||||
### Core Patterns
|
||||
|
||||
- Keep files focused on single responsibility
|
||||
- Create new service files for distinct directory integrations
|
||||
- Separate models into individual files when complex
|
||||
#### Directory Service Pattern
|
||||
|
||||
### TypeScript Conventions
|
||||
**Purpose**: Abstract different identity providers behind a common interface
|
||||
|
||||
**Import Patterns:**
|
||||
**Interface** (`src/abstractions/directory.service.ts`):
|
||||
|
||||
- Use path aliases (`@/`) for project imports
|
||||
- `@/` - project root
|
||||
- `@/jslib/` - jslib folder
|
||||
- ESLint enforces alphabetized import ordering with newlines between groups
|
||||
```typescript
|
||||
export interface IDirectoryService {
|
||||
getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]>;
|
||||
}
|
||||
```
|
||||
|
||||
**Type Safety:**
|
||||
**Implementations** in `src/services/directory-services/`:
|
||||
|
||||
- Avoid `any` types - use proper typing or `unknown` with type guards
|
||||
- Prefer interfaces for contracts, types for unions/intersections
|
||||
- Use strict null checks - handle `null` and `undefined` explicitly
|
||||
- Leverage TypeScript's type inference where appropriate
|
||||
- `ldap-directory.service.ts` - LDAP/Active Directory
|
||||
- `entra-id-directory.service.ts` - Microsoft Entra ID (Azure AD)
|
||||
- `gsuite-directory.service.ts` - Google Workspace
|
||||
- `okta-directory.service.ts` - Okta
|
||||
- `onelogin-directory.service.ts` - OneLogin
|
||||
|
||||
**Configuration:**
|
||||
**Factory** (`src/services/directory-factory.service.ts`):
|
||||
|
||||
- Use configuration files or environment variables
|
||||
- Never hardcode URLs or configuration values
|
||||
```typescript
|
||||
createService(type: DirectoryType): IDirectoryService
|
||||
```
|
||||
|
||||
## Security Best Practices
|
||||
#### State Service Pattern
|
||||
|
||||
**Credential Handling:**
|
||||
**Purpose**: Manage persistent state and credential storage
|
||||
|
||||
- Never log directory service credentials, API keys, or tokens
|
||||
- Use secure storage mechanisms for sensitive data
|
||||
- Credentials should never be hardcoded
|
||||
- Store credentials encrypted, never in plain text
|
||||
**Implementation** (`src/services/state.service.ts`):
|
||||
|
||||
**Sensitive Data:**
|
||||
- Configuration and sync settings stored in LowDB (JSON file)
|
||||
- Sensitive data (passwords, API keys) stored in system keychain
|
||||
- File locking via `proper-lockfile` to prevent concurrent access corruption
|
||||
- Platform-specific app data directories:
|
||||
- macOS: `~/Library/Application Support/Bitwarden Directory Connector`
|
||||
- Windows: `%APPDATA%/Bitwarden Directory Connector`
|
||||
- Linux: `~/.config/Bitwarden Directory Connector` or `$XDG_CONFIG_HOME`
|
||||
|
||||
- User and group data from directories should be handled securely
|
||||
- Avoid exposing sensitive information in error messages
|
||||
- Sanitize data before logging
|
||||
- Be cautious with data persistence
|
||||
---
|
||||
|
||||
**Input Validation:**
|
||||
## Development Guide
|
||||
|
||||
- Validate and sanitize data from external directory services
|
||||
- Check for injection vulnerabilities (LDAP injection, etc.)
|
||||
- Validate configuration inputs from users
|
||||
### Adding a New Directory Service
|
||||
|
||||
**API Security:**
|
||||
**1. Create the enum value** (`src/enums/directoryType.ts`)
|
||||
|
||||
- Ensure authentication flows are implemented correctly
|
||||
- Verify SSL/TLS is used for all external connections
|
||||
- Check for secure token storage and refresh mechanisms
|
||||
```typescript
|
||||
export enum DirectoryType {
|
||||
Ldap = 0,
|
||||
EntraID = 1,
|
||||
GSuite = 2,
|
||||
Okta = 3,
|
||||
OneLogin = 4,
|
||||
NewProvider = 5, // Add here
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
**2. Create the configuration model** (`src/models/newProviderConfiguration.ts`)
|
||||
|
||||
**Best Practices:**
|
||||
```typescript
|
||||
export class NewProviderConfiguration {
|
||||
apiUrl: string;
|
||||
apiToken: string;
|
||||
// Provider-specific settings
|
||||
}
|
||||
```
|
||||
|
||||
1. **Try-catch for async operations** - Always wrap external API calls
|
||||
2. **Meaningful error messages** - Provide context for debugging
|
||||
3. **Error propagation** - Don't swallow errors silently
|
||||
4. **User-facing errors** - Separate user messages from developer logs
|
||||
**3. Implement the directory service** (`src/services/directory-services/newprovider-directory.service.ts`)
|
||||
|
||||
## Performance Best Practices
|
||||
```typescript
|
||||
import { IDirectoryService } from "@/src/abstractions/directory.service";
|
||||
import { GroupEntry } from "@/src/models/groupEntry";
|
||||
import { UserEntry } from "@/src/models/userEntry";
|
||||
import { BaseDirectoryService } from "./base-directory.service";
|
||||
|
||||
**Large Dataset Handling:**
|
||||
export class NewProviderDirectoryService extends BaseDirectoryService implements IDirectoryService {
|
||||
constructor(
|
||||
private logService: LogService,
|
||||
private i18nService: I18nService,
|
||||
private stateService: StateService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
- Use pagination for large user/group lists
|
||||
- Avoid loading entire datasets into memory at once
|
||||
- Consider streaming or batch processing for large operations
|
||||
async getEntries(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||
const config = await this.stateService.getDirectory<NewProviderConfiguration>(
|
||||
DirectoryType.NewProvider,
|
||||
);
|
||||
const syncConfig = await this.stateService.getSync();
|
||||
|
||||
**API Rate Limiting:**
|
||||
const groups: GroupEntry[] = [];
|
||||
const users: UserEntry[] = [];
|
||||
|
||||
- Respect rate limits for Microsoft Graph API, Google Admin SDK, etc.
|
||||
- Consider batching large API calls where necessary
|
||||
// Fetch from provider API
|
||||
// Apply filters using inherited filter methods
|
||||
|
||||
**Memory Management:**
|
||||
return [groups, users];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- Close connections and clean up resources
|
||||
- Remove event listeners when components are destroyed
|
||||
- Be cautious with caching large datasets
|
||||
**4. Register in the factory** (`src/services/directory-factory.service.ts`)
|
||||
|
||||
```typescript
|
||||
case DirectoryType.NewProvider:
|
||||
return new NewProviderDirectoryService(
|
||||
this.logService,
|
||||
this.i18nService,
|
||||
this.stateService
|
||||
);
|
||||
```
|
||||
|
||||
**5. Add state service support** (`src/services/state.service.ts`)
|
||||
|
||||
```typescript
|
||||
// Add to secure storage keys if credentials involved
|
||||
// Add configuration getter/setter methods
|
||||
```
|
||||
|
||||
**6. Write tests** (`src/services/directory-services/newprovider-directory.service.spec.ts`)
|
||||
|
||||
### Common Patterns
|
||||
|
||||
#### Error Handling with State Rollback
|
||||
|
||||
```typescript
|
||||
async sync(force: boolean, test: boolean): Promise<[GroupEntry[], UserEntry[]]> {
|
||||
// Store initial state for rollback
|
||||
const startingUserDelta = await this.stateService.getUserDelta();
|
||||
const startingGroupDelta = await this.stateService.getGroupDelta();
|
||||
|
||||
try {
|
||||
// Perform sync operations
|
||||
const [groups, users] = await this.directoryService.getEntries(force, test);
|
||||
// ... process and submit
|
||||
return [groups, users];
|
||||
} catch (e) {
|
||||
if (!test) {
|
||||
// Rollback deltas on failure
|
||||
await this.stateService.setUserDelta(startingUserDelta);
|
||||
await this.stateService.setGroupDelta(startingGroupDelta);
|
||||
}
|
||||
this.messagingService.send("dirSyncCompleted", { successfully: false });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Filter Processing
|
||||
|
||||
```typescript
|
||||
// In BaseDirectoryService
|
||||
protected buildIncludeSet(filter: string): Set<string> {
|
||||
// Parse filter like "include:user1@example.com,user2@example.com"
|
||||
}
|
||||
|
||||
protected buildExcludeSet(filter: string): Set<string> {
|
||||
// Parse filter like "exclude:user1@example.com"
|
||||
}
|
||||
|
||||
protected shouldIncludeUser(user: UserEntry, include: Set<string>, exclude: Set<string>): boolean {
|
||||
if (exclude.has(user.email)) return false;
|
||||
if (include.size === 0) return true;
|
||||
return include.has(user.email);
|
||||
}
|
||||
```
|
||||
|
||||
### Running the Desktop GUI (Development)
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run rebuild # Rebuild native modules (keytar)
|
||||
npm run electron # Run GUI with hot reload
|
||||
```
|
||||
|
||||
### Running the CLI (Development)
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run build:cli:watch # Build CLI with watch mode
|
||||
node ./build-cli/bwdc.js --help # Run CLI commands
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Models
|
||||
|
||||
### Core Types
|
||||
|
||||
```typescript
|
||||
// Base entry class (src/models/entry.ts)
|
||||
abstract class Entry {
|
||||
referenceId: string; // Unique ID within the directory (e.g., DN for LDAP)
|
||||
externalId: string; // ID used for Bitwarden import
|
||||
}
|
||||
|
||||
// User entry (src/models/userEntry.ts)
|
||||
class UserEntry extends Entry {
|
||||
email: string;
|
||||
disabled: boolean;
|
||||
deleted: boolean;
|
||||
}
|
||||
|
||||
// Group entry (src/models/groupEntry.ts)
|
||||
class GroupEntry extends Entry {
|
||||
name: string;
|
||||
userMemberExternalIds: Set<string>; // External IDs of member users
|
||||
groupMemberReferenceIds: Set<string>; // Reference IDs of nested groups
|
||||
users: UserEntry[]; // Populated for display/simulation
|
||||
}
|
||||
```
|
||||
|
||||
### Directory Type Enum
|
||||
|
||||
```typescript
|
||||
// src/enums/directoryType.ts
|
||||
enum DirectoryType {
|
||||
Ldap = 0,
|
||||
EntraID = 1,
|
||||
GSuite = 2,
|
||||
Okta = 3,
|
||||
OneLogin = 4,
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Models
|
||||
|
||||
Each directory provider has a configuration class in `src/models/`:
|
||||
|
||||
- `LdapConfiguration` - hostname, port, SSL/TLS, bind credentials, auth mode
|
||||
- `EntraIdConfiguration` - tenant, client ID, secret key
|
||||
- `GSuiteConfiguration` - domain, admin user, client email, private key
|
||||
- `OktaConfiguration` - organization URL, API token
|
||||
- `OneLoginConfiguration` - client ID, client secret, region
|
||||
|
||||
### Sync Configuration
|
||||
|
||||
```typescript
|
||||
// src/models/syncConfiguration.ts
|
||||
interface SyncConfiguration {
|
||||
users: boolean; // Sync users
|
||||
groups: boolean; // Sync groups
|
||||
interval: number; // Minutes between syncs (minimum 5)
|
||||
userFilter: string; // Include/exclude filter
|
||||
groupFilter: string; // Include/exclude filter
|
||||
removeDisabled: boolean; // Remove disabled users from org
|
||||
overwriteExisting: boolean; // Overwrite existing entries
|
||||
largeImport: boolean; // Enable for >2000 entries
|
||||
// LDAP-specific
|
||||
groupObjectClass: string;
|
||||
userObjectClass: string;
|
||||
groupPath: string;
|
||||
userPath: string;
|
||||
// ... additional LDAP attributes
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security & Configuration
|
||||
|
||||
### Security Rules
|
||||
|
||||
**MANDATORY - These rules have no exceptions:**
|
||||
|
||||
1. **Never log credentials**: API keys, passwords, tokens, and secrets must never appear in logs
|
||||
2. **Never hardcode secrets**: All URLs, credentials, and sensitive data must come from configuration
|
||||
3. **Use KeytarSecureStorageService**: All credentials must be stored in the system keychain
|
||||
4. **Validate external data**: Sanitize all data received from directory services
|
||||
5. **LDAP injection prevention**: Be cautious with user-provided LDAP filters
|
||||
|
||||
### Secure Storage Keys
|
||||
|
||||
The following are stored in the system keychain (not plain JSON):
|
||||
|
||||
- `ldapPassword` - LDAP bind password
|
||||
- `gsuitePrivateKey` - Google Workspace private key
|
||||
- `entraKey` - Microsoft Entra ID client secret
|
||||
- `oktaToken` - Okta API token
|
||||
- `oneLoginClientSecret` - OneLogin client secret
|
||||
- User/group delta tokens
|
||||
- Sync hashes
|
||||
|
||||
### Environment Variables
|
||||
|
||||
| Variable | Required | Description | Example |
|
||||
| ------------------------------------------ | -------- | ---------------------------------------- | -------------------- |
|
||||
| `BITWARDENCLI_CONNECTOR_APPDATA_DIR` | No | CLI app data directory override | `/custom/path` |
|
||||
| `BITWARDEN_CONNECTOR_APPDATA_DIR` | No | GUI app data directory override | `/custom/path` |
|
||||
| `BITWARDENCLI_CONNECTOR_PLAINTEXT_SECRETS` | No | Store secrets in plain text (debug only) | `true` |
|
||||
| `BITWARDENCLI_CONNECTOR_DEBUG` | No | Enable debug logging | `true` |
|
||||
| `BW_CLIENTID` | No | CLI login client ID | `organization.xxxxx` |
|
||||
| `BW_CLIENTSECRET` | No | CLI login client secret | `xxxxx` |
|
||||
| `BW_NOINTERACTION` | No | Disable interactive prompts | `true` |
|
||||
| `BW_PRETTY` | No | Pretty-print JSON output | `true` |
|
||||
| `BW_RAW` | No | Raw output (no formatting) | `true` |
|
||||
| `BW_RESPONSE` | No | JSON response format | `true` |
|
||||
| `BW_QUIET` | No | Suppress stdout | `true` |
|
||||
|
||||
### Authentication & Authorization
|
||||
|
||||
- **API Token Authentication**: Uses organization `clientId` + `clientSecret`
|
||||
- **Token Storage**: Access tokens and refresh tokens stored securely via Keytar
|
||||
- **Token Refresh**: Automatic refresh when access token expires
|
||||
- **Auth Service**: `src/services/auth.service.ts` handles the authentication flow
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
**Framework:**
|
||||
### Test Structure
|
||||
|
||||
- Jest with jest-preset-angular
|
||||
- jest-mock-extended for type-safe mocks with `mock<Type>()`
|
||||
```
|
||||
src/
|
||||
├── services/
|
||||
│ ├── sync.service.spec.ts # Unit tests (colocated)
|
||||
│ ├── sync.service.integration.spec.ts # Integration tests
|
||||
│ └── directory-services/
|
||||
│ ├── ldap-directory.service.spec.ts
|
||||
│ └── ldap-directory.service.integration.spec.ts
|
||||
utils/
|
||||
└── openldap/
|
||||
├── config-fixtures.ts # Test configuration helpers
|
||||
├── user-fixtures.ts # Expected user data
|
||||
├── group-fixtures.ts # Expected group data
|
||||
├── certs/ # TLS certificates
|
||||
└── docker-compose.yml # LDAP container config
|
||||
```
|
||||
|
||||
**Test Organization:**
|
||||
### Writing Tests
|
||||
|
||||
- Tests colocated with source files
|
||||
- `*.spec.ts` - Unit tests for individual components/services
|
||||
- `*.integration.spec.ts` - Integration tests against live directory services
|
||||
- Test helpers located in `utils/` directory
|
||||
**Unit Test Template**:
|
||||
|
||||
**Test Naming:**
|
||||
```typescript
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
- Descriptive, human-readable test names
|
||||
- Example: `'should return empty array when no users exist in directory'`
|
||||
describe("ServiceName", () => {
|
||||
let logService: MockProxy<LogService>;
|
||||
let stateService: MockProxy<StateService>;
|
||||
let service: ServiceUnderTest;
|
||||
|
||||
**Test Coverage:**
|
||||
beforeEach(() => {
|
||||
logService = mock();
|
||||
stateService = mock();
|
||||
service = new ServiceUnderTest(logService, stateService);
|
||||
});
|
||||
|
||||
- New features must include tests
|
||||
- Bug fixes should include regression tests
|
||||
- Changes to core sync logic or directory specific logic require integration tests
|
||||
it("should do something", async () => {
|
||||
// Arrange
|
||||
stateService.getSomeValue.mockResolvedValue(expectedValue);
|
||||
|
||||
**Testing Approach:**
|
||||
// Act
|
||||
const result = await service.doSomething();
|
||||
|
||||
- **Unit tests**: Mock external API calls using jest-mock-extended
|
||||
- **Integration tests**: Use live directory services (Docker containers or configured cloud services)
|
||||
- Focus on critical paths (authentication, sync, data transformation)
|
||||
- Test error scenarios and edge cases (empty results, malformed data, connection failures), not just happy paths
|
||||
// Assert
|
||||
expect(result).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## Directory Service Patterns
|
||||
**Integration Test Template** (see `ldap-directory.service.integration.spec.ts`):
|
||||
|
||||
### IDirectoryService Interface
|
||||
```typescript
|
||||
// Requires Docker containers running
|
||||
// npm run test:integration:setup
|
||||
|
||||
All directory services implement this core interface with methods:
|
||||
describe("ldapDirectoryService", () => {
|
||||
let stateService: MockProxy<StateService>;
|
||||
let directoryService: LdapDirectoryService;
|
||||
|
||||
- `getUsers()` - Retrieve users from directory and transform them into standard objects
|
||||
- `getGroups()` - Retrieve groups from directory and transform them into standard objects
|
||||
- Connection and authentication handling
|
||||
beforeEach(() => {
|
||||
stateService = mock();
|
||||
stateService.getDirectoryType.mockResolvedValue(DirectoryType.Ldap);
|
||||
stateService.getDirectory
|
||||
.calledWith(DirectoryType.Ldap)
|
||||
.mockResolvedValue(getLdapConfiguration());
|
||||
});
|
||||
|
||||
### Service-Specific Implementations
|
||||
it("syncs users and groups", async () => {
|
||||
const result = await directoryService.getEntries(true, true);
|
||||
expect(result).toEqual([groupFixtures, userFixtures]);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
Each directory service has unique authentication and query patterns:
|
||||
### Running Tests
|
||||
|
||||
- **LDAP**: Direct LDAP queries, bind authentication
|
||||
- **Microsoft Entra ID**: Microsoft Graph API, OAuth tokens
|
||||
- **Google Workspace**: Google Admin SDK, service account credentials
|
||||
- **Okta/OneLogin**: REST APIs with API tokens
|
||||
```bash
|
||||
npm test # All unit tests (excludes integration)
|
||||
npm test -- path/to/file.spec.ts # Single test file
|
||||
npm run test:watch # Watch mode
|
||||
|
||||
# Integration tests
|
||||
npm run test:integration:setup # Start Docker containers
|
||||
npm run test:integration # Run integration tests
|
||||
npm run test:integration:watch # Watch mode for integration
|
||||
```
|
||||
|
||||
### Test Environment
|
||||
|
||||
- **Mocking**: `jest-mock-extended` with `mock<Type>()` for type-safe mocks
|
||||
- **Alternative**: `@fluffy-spoon/substitute` available for some tests
|
||||
- **Integration**: Docker containers for LDAP (OpenLDAP)
|
||||
- **Fixtures**: Located in `utils/openldap/`
|
||||
|
||||
---
|
||||
|
||||
## Code Style & Standards
|
||||
|
||||
### Formatting
|
||||
|
||||
- **Prettier**: Auto-formatting enforced via pre-commit hooks
|
||||
- **Config**: `.prettierrc` in project root
|
||||
|
||||
### Naming Conventions
|
||||
|
||||
- `camelCase` for: variables, functions, method names
|
||||
- `PascalCase` for: classes, interfaces, types, enums
|
||||
- `SCREAMING_SNAKE_CASE` for: constants (rare in this codebase)
|
||||
|
||||
### Imports
|
||||
|
||||
**Path Aliases:**
|
||||
|
||||
- `@/` maps to project root
|
||||
- Example: `import { SyncService } from "@/src/services/sync.service"`
|
||||
|
||||
**Import Order (ESLint enforced):**
|
||||
|
||||
1. External packages (node_modules)
|
||||
2. jslib imports (`@/jslib/...`)
|
||||
3. Project imports (`@/src/...`)
|
||||
4. Alphabetized within each group with newlines between groups
|
||||
|
||||
```typescript
|
||||
// External
|
||||
import { mock, MockProxy } from "jest-mock-extended";
|
||||
|
||||
// jslib
|
||||
import { LogService } from "@/jslib/common/src/abstractions/log.service";
|
||||
|
||||
// Project
|
||||
import { DirectoryType } from "@/src/enums/directoryType";
|
||||
import { SyncService } from "@/src/services/sync.service";
|
||||
```
|
||||
|
||||
### Comments
|
||||
|
||||
- Avoid unnecessary comments; code should be self-documenting
|
||||
- Use JSDoc only for public APIs that need documentation
|
||||
- Inline comments for complex logic only
|
||||
|
||||
### Pre-commit Hooks
|
||||
|
||||
- **Husky**: Runs `lint-staged` on commit
|
||||
- **lint-staged**: Runs Prettier on all files, ESLint on TypeScript files
|
||||
|
||||
```bash
|
||||
npm run lint # Check ESLint + Prettier
|
||||
npm run lint:fix # Auto-fix ESLint issues
|
||||
npm run prettier # Auto-format with Prettier
|
||||
npm run test:types # TypeScript type checking
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
### DO
|
||||
|
||||
- ✅ Use `KeytarSecureStorageService` for all credential storage
|
||||
- ✅ Implement `IDirectoryService` interface for new directory providers
|
||||
- ✅ Use the factory pattern via `DirectoryFactoryService`
|
||||
- ✅ Write unit tests with `jest-mock-extended` mocks
|
||||
- ✅ Handle errors with state rollback (delta tokens)
|
||||
- ✅ Use path aliases (`@/src/...`) for imports
|
||||
- ✅ Validate data from external directory services
|
||||
- ✅ Use `force` and `test` parameters consistently in sync methods
|
||||
|
||||
### DON'T
|
||||
|
||||
- ❌ Log credentials, API keys, or tokens
|
||||
- ❌ Hardcode URLs, secrets, or configuration values
|
||||
- ❌ Store sensitive data in LowDB (JSON) - use Keytar
|
||||
- ❌ Skip input validation for LDAP filters (injection risk)
|
||||
- ❌ Use `any` types without explicit justification
|
||||
- ❌ Add new code to `jslib/` (legacy, read-only)
|
||||
- ❌ Ignore delta token rollback on sync failure
|
||||
- ❌ Bypass `overwriteExisting` validation for batch imports (>2000 entries)
|
||||
|
||||
---
|
||||
|
||||
## Deployment
|
||||
|
||||
### Building
|
||||
|
||||
**Desktop GUI (Electron):**
|
||||
|
||||
```bash
|
||||
npm run build # Build main + renderer
|
||||
npm run build:dist # Full distribution build
|
||||
npm run dist:win # Windows installer
|
||||
npm run dist:mac # macOS installer
|
||||
npm run dist:lin # Linux packages (AppImage, RPM)
|
||||
```
|
||||
|
||||
**CLI Tool:**
|
||||
|
||||
```bash
|
||||
npm run build:cli:prod # Production build
|
||||
npm run dist:cli:win # Windows executable
|
||||
npm run dist:cli:mac # macOS executable
|
||||
npm run dist:cli:lin # Linux executable
|
||||
```
|
||||
|
||||
### Versioning
|
||||
|
||||
Follow semantic versioning: `MAJOR.MINOR.PATCH`
|
||||
|
||||
- Version format: `YYYY.MM.PATCH` (e.g., `2025.12.0`)
|
||||
- Managed in `package.json`
|
||||
|
||||
### Publishing
|
||||
|
||||
- **CI/CD**: GitHub Actions workflows in `.github/workflows/`
|
||||
- **build.yml**: Multi-platform builds with code signing
|
||||
- **release.yml**: Version bumping and publishing
|
||||
- **Code Signing**: Azure Key Vault (Windows), App Store Connect (macOS)
|
||||
- **Auto-update**: Electron Updater for GUI application
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### LDAP Connection Failures
|
||||
|
||||
**Problem**: Cannot connect to LDAP server, timeout or connection refused
|
||||
|
||||
**Solution**:
|
||||
|
||||
1. Verify hostname and port are correct
|
||||
2. Check SSL/TLS settings match server configuration
|
||||
3. For StartTLS, ensure SSL is enabled and use the non-secure port (389)
|
||||
4. For LDAPS, use port 636 and provide CA certificate path
|
||||
|
||||
#### Keytar/Native Module Issues
|
||||
|
||||
**Problem**: `Error: Module did not self-register` or keytar-related crashes
|
||||
|
||||
**Solution**:
|
||||
|
||||
```bash
|
||||
npm run rebuild # Rebuild native modules for current Electron version
|
||||
npm run reset # Full reset of keytar module
|
||||
```
|
||||
|
||||
#### Sync Hash Mismatch
|
||||
|
||||
**Problem**: Sync runs but no changes appear in Bitwarden
|
||||
|
||||
**Solution**: The sync service skips if the hash matches the previous sync. Use force sync:
|
||||
|
||||
```bash
|
||||
bwdc sync --force # CLI
|
||||
# Or clear cache
|
||||
bwdc clear-cache
|
||||
```
|
||||
|
||||
#### Large Import Failures
|
||||
|
||||
**Problem**: Sync fails for organizations with >2000 users/groups
|
||||
|
||||
**Solution**: Enable `largeImport` in sync settings. Note: `overwriteExisting` is incompatible with batch mode.
|
||||
|
||||
### Debug Tips
|
||||
|
||||
- Enable debug logging: `BITWARDENCLI_CONNECTOR_DEBUG=true`
|
||||
- View data file location: `bwdc data-file`
|
||||
- Test sync without making changes: `bwdc test`
|
||||
- Check last sync times: `bwdc last-sync users` / `bwdc last-sync groups`
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [Architectural Decision Records (ADRs)](https://contributing.bitwarden.com/architecture/adr/)
|
||||
- [Contributing Guidelines](https://contributing.bitwarden.com/contributing/)
|
||||
- [Code Style](https://contributing.bitwarden.com/contributing/code-style/)
|
||||
- [Security Whitepaper](https://bitwarden.com/help/bitwarden-security-white-paper/)
|
||||
- [Security Definitions](https://contributing.bitwarden.com/architecture/security/definitions)
|
||||
### Official Documentation
|
||||
|
||||
- [Directory Sync CLI Documentation](https://bitwarden.com/help/directory-sync-cli/)
|
||||
- [Directory Connector Help](https://bitwarden.com/help/directory-sync/)
|
||||
|
||||
### Internal Documentation
|
||||
|
||||
- [Bitwarden Contributing Guidelines](https://contributing.bitwarden.com/contributing/)
|
||||
- [Code Style Guide](https://contributing.bitwarden.com/contributing/code-style/)
|
||||
|
||||
### Tools & Libraries
|
||||
|
||||
- [ldapts](https://github.com/ldapts/ldapts) - LDAP client for Node.js
|
||||
- [Keytar](https://github.com/atom/node-keytar) - Native keychain access
|
||||
- [Commander.js](https://github.com/tj/commander.js) - CLI framework
|
||||
- [LowDB](https://github.com/typicode/lowdb) - JSON database
|
||||
- [Microsoft Graph Client](https://github.com/microsoftgraph/msgraph-sdk-javascript) - Entra ID API
|
||||
- [Google APIs](https://github.com/googleapis/google-api-nodejs-client) - GSuite API
|
||||
|
||||
30
.claude/commands/code-explainer.md
Normal file
30
.claude/commands/code-explainer.md
Normal file
@@ -0,0 +1,30 @@
|
||||
---
|
||||
description: "Provides a brief explanation of the code attached, including key components, notable patterns, and a code walkthrough."
|
||||
---
|
||||
|
||||
# Code Explainer
|
||||
|
||||
Provide a brief explanation of the code attached. I'm trying to better understand it.
|
||||
|
||||
## Key Components
|
||||
|
||||
- Main classes/functions and their roles
|
||||
- Important dependencies
|
||||
- Critical flows
|
||||
|
||||
## Notable Patterns
|
||||
|
||||
- Design patterns used
|
||||
- Architecture decisions
|
||||
- Important abstractions
|
||||
|
||||
## Code Walkthrough
|
||||
|
||||
- How it works
|
||||
- Key decision points
|
||||
- Important considerations
|
||||
|
||||
## Gotchas & Tips
|
||||
|
||||
- Edge cases to watch for
|
||||
- Performance considerations
|
||||
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -56,7 +56,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Linux Zip to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-linux-${{ env._PACKAGE_VERSION }}.zip
|
||||
@@ -134,7 +134,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -182,7 +182,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Upload Mac Zip to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-macos-${{ env._PACKAGE_VERSION }}.zip
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
choco install checksum --no-progress
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -258,7 +258,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload Windows Zip to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||
path: ./dist-cli/bwdc-windows-${{ env._PACKAGE_VERSION }}.zip
|
||||
@@ -284,7 +284,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -338,28 +338,28 @@ jobs:
|
||||
SIGNING_CERT_NAME: ${{ steps.retrieve-secrets.outputs.code-signing-cert-name }}
|
||||
|
||||
- name: Upload Portable Executable to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||
path: ./dist/Bitwarden-Connector-Portable-${{ env._PACKAGE_VERSION }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Installer Executable to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload Installer Executable Blockmap to GitHub
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||
path: ./dist/Bitwarden-Connector-Installer-${{ env._PACKAGE_VERSION }}.exe.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest.yml
|
||||
path: ./dist/latest.yml
|
||||
@@ -384,7 +384,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -411,14 +411,14 @@ jobs:
|
||||
run: npm run dist:lin
|
||||
|
||||
- name: Upload AppImage
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-x86_64.AppImage
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest-linux.yml
|
||||
path: ./dist/latest-linux.yml
|
||||
@@ -444,7 +444,7 @@ jobs:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -542,28 +542,28 @@ jobs:
|
||||
CSC_FOR_PULL_REQUEST: true
|
||||
|
||||
- name: Upload .zip artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}-mac.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .dmg artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload .dmg Blockmap artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||
path: ./dist/Bitwarden-Connector-${{ env._PACKAGE_VERSION }}.dmg.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload latest auto-update artifact
|
||||
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: latest-mac.yml
|
||||
path: ./dist/latest-mac.yml
|
||||
|
||||
8
.github/workflows/integration-test.yml
vendored
8
.github/workflows/integration-test.yml
vendored
@@ -52,7 +52,7 @@ jobs:
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -129,7 +129,7 @@ jobs:
|
||||
|
||||
- name: Report test results
|
||||
id: report
|
||||
uses: dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3 # v2.1.1
|
||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
|
||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||
# PRs from the repository and all other events are OK.
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||
@@ -140,7 +140,7 @@ jobs:
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5.2.0
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -75,7 +75,7 @@ jobs:
|
||||
|
||||
- name: Create release
|
||||
if: ${{ inputs.release_type != 'Dry Run' }}
|
||||
uses: ncipollo/release-action@cdcc88a9acf3ca41c16c37bb7d21b9ad48560d87 # v1.15.0
|
||||
uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
|
||||
env:
|
||||
PKG_VERSION: ${{ needs.setup.outputs.release_version }}
|
||||
with:
|
||||
|
||||
2
.github/workflows/review-code.yml
vendored
2
.github/workflows/review-code.yml
vendored
@@ -2,7 +2,7 @@ name: Code Review
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, labeled]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
||||
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
echo "node_version=$NODE_VERSION" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
|
||||
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
cache: 'npm'
|
||||
cache-dependency-path: '**/package-lock.json'
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
run: npm run test --coverage
|
||||
|
||||
- name: Report test results
|
||||
uses: dorny/test-reporter@dc3a92680fcc15842eef52e8c4606ea7ce6bd3f3 # v2.1.1
|
||||
uses: dorny/test-reporter@fe45e9537387dac839af0d33ba56eed8e24189e8 # v2.3.0
|
||||
# This will skip the job if it's a pull request from a fork, because that won't have permission to upload test results.
|
||||
# PRs from the repository and all other events are OK.
|
||||
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository) && !cancelled()
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
fail-on-error: true
|
||||
|
||||
- name: Upload coverage to codecov.io
|
||||
uses: codecov/codecov-action@5a605bd92782ce0810fa3b8acc235c921b497052 # v5.2.0
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
|
||||
|
||||
- name: Upload results to codecov.io
|
||||
uses: codecov/test-results-action@4e79e65778be1cecd5df25e14af1eafb6df80ea9 # v1.0.2
|
||||
uses: codecov/test-results-action@0fa95f0e1eeaafde2c782583b36b28ad0d8c77d3 # v1.2.1
|
||||
|
||||
2
.github/workflows/version-bump.yml
vendored
2
.github/workflows/version-bump.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
uses: bitwarden/gh-actions/azure-logout@main
|
||||
|
||||
- name: Generate GH App token
|
||||
uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ steps.get-kv-secrets.outputs.BW-GHAPP-ID }}
|
||||
|
||||
156
ESM_MIGRATION_PLAN.md
Normal file
156
ESM_MIGRATION_PLAN.md
Normal file
@@ -0,0 +1,156 @@
|
||||
# ESM Migration Plan
|
||||
|
||||
## Migration Status: Partial Success
|
||||
|
||||
The ESM migration has been **partially completed**. The source code is now ESM-compatible with `"type": "module"` in package.json, and webpack outputs CommonJS bundles (`.cjs`) for Node.js compatibility.
|
||||
|
||||
### What Works
|
||||
|
||||
- ✅ CLI build (`bwdc.cjs`) - builds and runs successfully
|
||||
- ✅ Electron main process (`main.cjs`) - builds successfully
|
||||
- ✅ All 130 tests pass
|
||||
- ✅ Source code uses ESM syntax (import/export)
|
||||
|
||||
### What Doesn't Work
|
||||
|
||||
- ❌ Electron renderer build - **pre-existing type errors in jslib** (not caused by this migration)
|
||||
|
||||
The renderer build was failing with 37 TypeScript errors in `jslib/` **before** the ESM migration began. These are ArrayBuffer/SharedArrayBuffer type compatibility issues in the jslib submodule that need to be addressed separately.
|
||||
|
||||
---
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "module",
|
||||
"main": "main.cjs"
|
||||
}
|
||||
```
|
||||
|
||||
### 2. tsconfig.json
|
||||
|
||||
```json
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "ES2020",
|
||||
"skipLibCheck": true,
|
||||
"noEmitOnError": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Webpack Configurations
|
||||
|
||||
**CLI (webpack.cli.cjs)**
|
||||
|
||||
- Output changed to `.cjs` extension
|
||||
- Added `transpileOnly: true` to ts-loader for faster builds
|
||||
|
||||
**Main (webpack.main.cjs)**
|
||||
|
||||
- Output changed to `.cjs` extension
|
||||
- Added `transpileOnly: true` to ts-loader
|
||||
|
||||
**Renderer (webpack.renderer.cjs)**
|
||||
|
||||
- Created separate `tsconfig.renderer.json` to isolate Angular compilation
|
||||
- Removed ESM output experiments (not compatible with Angular's webpack plugin)
|
||||
|
||||
### 4. src-cli/package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"bwdc": "../build-cli/bwdc.cjs"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. New File: tsconfig.renderer.json
|
||||
|
||||
Dedicated TypeScript config for Angular renderer to isolate from jslib type issues.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Decision
|
||||
|
||||
### Why CJS Output Instead of ESM Output?
|
||||
|
||||
The migration uses a **hybrid approach**:
|
||||
|
||||
- **Source code**: ESM syntax (`import`/`export`)
|
||||
- **Build output**: CommonJS (`.cjs` files)
|
||||
|
||||
This approach was chosen because:
|
||||
|
||||
1. **lowdb v1 incompatibility**: The legacy lowdb v1 used in jslib doesn't work properly with ESM output due to lodash interop issues
|
||||
|
||||
2. **Native module compatibility**: keytar and other native modules work better with CJS
|
||||
|
||||
3. **Electron compatibility**: Electron's main process ESM support is still maturing
|
||||
|
||||
4. **jslib constraints**: The jslib submodule is read-only and contains CJS-only patterns
|
||||
|
||||
The webpack bundler transpiles ESM source to CJS output, giving us modern syntax in the codebase while maintaining runtime compatibility.
|
||||
|
||||
---
|
||||
|
||||
## Blocking Issues for Full ESM
|
||||
|
||||
### 1. jslib Submodule (Read-Only)
|
||||
|
||||
The jslib folder contains:
|
||||
|
||||
- `lowdb` v1.0.0 usage (CJS-only, v7 is ESM but has breaking API changes)
|
||||
- `node-fetch` v2.7.0 usage (CJS-only, v3 is ESM-only)
|
||||
- Pre-existing TypeScript errors (ArrayBuffer type mismatches)
|
||||
|
||||
### 2. Angular Webpack Plugin
|
||||
|
||||
The `@ngtools/webpack` plugin does its own TypeScript compilation and doesn't support `transpileOnly` mode, so it surfaces type errors from jslib.
|
||||
|
||||
---
|
||||
|
||||
## Future Work
|
||||
|
||||
To complete full ESM migration:
|
||||
|
||||
1. **Update jslib submodule** - Fix type errors, upgrade to ESM-compatible dependencies
|
||||
2. **Upgrade lowdb** - From v1 to v7 (requires rewriting storage layer)
|
||||
3. **Remove node-fetch** - Use native `fetch` (Node 18+) or upgrade to v3
|
||||
4. **Enable ESM output** - Once dependencies are updated, change webpack output to ESM
|
||||
|
||||
---
|
||||
|
||||
## Testing the Migration
|
||||
|
||||
```bash
|
||||
# Build CLI
|
||||
npm run build:cli
|
||||
node ./build-cli/bwdc.cjs --help
|
||||
|
||||
# Build Electron main
|
||||
npm run build:main
|
||||
|
||||
# Run tests
|
||||
npm test
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
| ------------------------ | ---------------------------------------------------- |
|
||||
| `package.json` | Added `"type": "module"`, changed main to `main.cjs` |
|
||||
| `tsconfig.json` | Added `skipLibCheck`, `noEmitOnError` |
|
||||
| `tsconfig.renderer.json` | New file for Angular compilation |
|
||||
| `webpack.cli.cjs` | Output to `.cjs`, added `transpileOnly` |
|
||||
| `webpack.main.cjs` | Output to `.cjs`, added `transpileOnly` |
|
||||
| `webpack.renderer.cjs` | Use separate tsconfig |
|
||||
| `src-cli/package.json` | Added `"type": "module"`, updated bin path |
|
||||
@@ -24,13 +24,20 @@ module.exports = {
|
||||
|
||||
roots: ["<rootDir>"],
|
||||
modulePaths: [compilerOptions.baseUrl],
|
||||
moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
moduleNameMapper: {
|
||||
...pathsToModuleNameMapper(compilerOptions.paths, { prefix: "<rootDir>/" }),
|
||||
// ESM compatibility: mock import.meta.url for tests
|
||||
"^(\\.{1,2}/.*)\\.js$": "$1",
|
||||
},
|
||||
setupFilesAfterEnv: ["<rootDir>/test.setup.ts"],
|
||||
// Workaround for a memory leak that crashes tests in CI:
|
||||
// https://github.com/facebook/jest/issues/9430#issuecomment-1149882002
|
||||
// Also anecdotally improves performance when run locally
|
||||
maxWorkers: 3,
|
||||
|
||||
// ESM support
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
|
||||
transform: {
|
||||
"^.+\\.tsx?$": [
|
||||
"jest-preset-angular",
|
||||
@@ -43,6 +50,8 @@ module.exports = {
|
||||
// Makes tests run faster and reduces size/rate of leak, but loses typechecking on test code
|
||||
// See https://bitwarden.atlassian.net/browse/EC-497 for more info
|
||||
isolatedModules: true,
|
||||
// ESM support
|
||||
useESM: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
75
package-lock.json
generated
75
package-lock.json
generated
@@ -99,14 +99,14 @@
|
||||
"prettier": "3.7.4",
|
||||
"rimraf": "6.1.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sass": "1.94.2",
|
||||
"sass": "1.97.1",
|
||||
"sass-loader": "16.0.5",
|
||||
"ts-jest": "29.4.1",
|
||||
"ts-loader": "9.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "4.2.0",
|
||||
"type-fest": "5.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"webpack": "5.103.0",
|
||||
"webpack": "5.104.1",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-merge": "6.0.1",
|
||||
"webpack-node-externals": "3.0.0",
|
||||
@@ -10087,9 +10087,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/baseline-browser-mapping": {
|
||||
"version": "2.8.31",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
|
||||
"integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
|
||||
"version": "2.9.12",
|
||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.12.tgz",
|
||||
"integrity": "sha512-Mij6Lij93pTAIsSYy5cyBQ975Qh9uLEc5rwGTpomiZeXZL9yIS6uORJakb3ScHgfs0serMMfIbXzokPMuEiRyw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@@ -10291,9 +10291,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/browserslist": {
|
||||
"version": "4.28.0",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
|
||||
"integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
|
||||
"version": "4.28.1",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
|
||||
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -10311,11 +10311,11 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"baseline-browser-mapping": "^2.8.25",
|
||||
"caniuse-lite": "^1.0.30001754",
|
||||
"electron-to-chromium": "^1.5.249",
|
||||
"baseline-browser-mapping": "^2.9.0",
|
||||
"caniuse-lite": "^1.0.30001759",
|
||||
"electron-to-chromium": "^1.5.263",
|
||||
"node-releases": "^2.0.27",
|
||||
"update-browserslist-db": "^1.1.4"
|
||||
"update-browserslist-db": "^1.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"browserslist": "cli.js"
|
||||
@@ -10750,9 +10750,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001757",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
|
||||
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
|
||||
"version": "1.0.30001762",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001762.tgz",
|
||||
"integrity": "sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -12898,9 +12898,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.262",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
|
||||
"integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
|
||||
"version": "1.5.267",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz",
|
||||
"integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -22860,9 +22860,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/sass": {
|
||||
"version": "1.94.2",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.94.2.tgz",
|
||||
"integrity": "sha512-N+7WK20/wOr7CzA2snJcUSSNTCzeCGUTFY3OgeQP3mZ1aj9NMQ0mSTXwlrnd89j33zzQJGqIN52GIOmYrfq46A==",
|
||||
"version": "1.97.1",
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.97.1.tgz",
|
||||
"integrity": "sha512-uf6HoO8fy6ClsrShvMgaKUn14f2EHQLQRtpsZZLeU/Mv0Q1K5P0+x2uvH6Cub39TVVbWNSrraUhDAoFph6vh0A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -24306,9 +24306,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.14",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz",
|
||||
"integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==",
|
||||
"version": "5.3.16",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz",
|
||||
"integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -25200,9 +25200,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/update-browserslist-db": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
|
||||
"integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
|
||||
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -25540,9 +25540,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
"version": "5.103.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz",
|
||||
"integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==",
|
||||
"version": "5.104.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.104.1.tgz",
|
||||
"integrity": "sha512-Qphch25abbMNtekmEGJmeRUhLDbe+QfiWTiqpKYkpCOWY64v9eyl+KRRLmqOFA2AvKPpc9DC6+u2n76tQLBoaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -25554,10 +25554,10 @@
|
||||
"@webassemblyjs/wasm-parser": "^1.14.1",
|
||||
"acorn": "^8.15.0",
|
||||
"acorn-import-phases": "^1.0.3",
|
||||
"browserslist": "^4.26.3",
|
||||
"browserslist": "^4.28.1",
|
||||
"chrome-trace-event": "^1.0.2",
|
||||
"enhanced-resolve": "^5.17.3",
|
||||
"es-module-lexer": "^1.2.1",
|
||||
"enhanced-resolve": "^5.17.4",
|
||||
"es-module-lexer": "^2.0.0",
|
||||
"eslint-scope": "5.1.1",
|
||||
"events": "^3.2.0",
|
||||
"glob-to-regexp": "^0.4.1",
|
||||
@@ -25568,7 +25568,7 @@
|
||||
"neo-async": "^2.6.2",
|
||||
"schema-utils": "^4.3.3",
|
||||
"tapable": "^2.3.0",
|
||||
"terser-webpack-plugin": "^5.3.11",
|
||||
"terser-webpack-plugin": "^5.3.16",
|
||||
"watchpack": "^2.4.4",
|
||||
"webpack-sources": "^3.3.3"
|
||||
},
|
||||
@@ -26280,6 +26280,13 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/webpack/node_modules/es-module-lexer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
|
||||
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/webpack/node_modules/eslint-scope": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"description": "Sync your user directory to your Bitwarden organization.",
|
||||
"version": "2025.12.0",
|
||||
"type": "module",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
@@ -16,7 +17,7 @@
|
||||
"url": "https://github.com/bitwarden/directory-connector"
|
||||
},
|
||||
"license": "GPL-3.0",
|
||||
"main": "main.js",
|
||||
"main": "main.cjs",
|
||||
"scripts": {
|
||||
"sub:init": "git submodule update --init --recursive",
|
||||
"sub:update": "git submodule update --remote",
|
||||
@@ -129,14 +130,14 @@
|
||||
"prettier": "3.7.4",
|
||||
"rimraf": "6.1.0",
|
||||
"rxjs": "7.8.2",
|
||||
"sass": "1.94.2",
|
||||
"sass": "1.97.1",
|
||||
"sass-loader": "16.0.5",
|
||||
"ts-jest": "29.4.1",
|
||||
"ts-loader": "9.5.2",
|
||||
"tsconfig-paths-webpack-plugin": "4.2.0",
|
||||
"type-fest": "5.3.0",
|
||||
"typescript": "5.8.3",
|
||||
"webpack": "5.103.0",
|
||||
"webpack": "5.104.1",
|
||||
"webpack-cli": "6.0.1",
|
||||
"webpack-merge": "6.0.1",
|
||||
"webpack-node-externals": "3.0.0",
|
||||
|
||||
@@ -3,16 +3,17 @@
|
||||
"productName": "Bitwarden Directory Connector",
|
||||
"description": "Sync your user directory to your Bitwarden organization.",
|
||||
"version": "2.9.5",
|
||||
"type": "module",
|
||||
"author": "Bitwarden Inc. <hello@bitwarden.com> (https://bitwarden.com)",
|
||||
"homepage": "https://bitwarden.com",
|
||||
"license": "GPL-3.0",
|
||||
"main": "main.js",
|
||||
"main": "main.mjs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/bitwarden/directory-connector"
|
||||
},
|
||||
"bin": {
|
||||
"bwdc": "../build-cli/bwdc.js"
|
||||
"bwdc": "../build-cli/bwdc.cjs"
|
||||
},
|
||||
"pkg": {
|
||||
"assets": "../build-cli/**/*"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"pretty": true,
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"target": "ES2016",
|
||||
"target": "ES2020",
|
||||
"module": "ES2020",
|
||||
"lib": ["es5", "es6", "es7", "dom"],
|
||||
"sourceMap": true,
|
||||
@@ -18,6 +18,8 @@
|
||||
"outDir": "dist",
|
||||
"baseUrl": ".",
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmitOnError": false,
|
||||
"paths": {
|
||||
"tldjs": ["./jslib/common/src/misc/tldjs.noop"],
|
||||
"@/*": ["./*"]
|
||||
|
||||
13
tsconfig.renderer.json
Normal file
13
tsconfig.renderer.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"angularCompilerOptions": {
|
||||
"strictTemplates": true,
|
||||
"preserveWhitespaces": true
|
||||
},
|
||||
"compilerOptions": {
|
||||
"skipLibCheck": true,
|
||||
"noEmitOnError": false
|
||||
},
|
||||
"include": ["src/app"],
|
||||
"exclude": ["jslib", "**/*.spec.ts"]
|
||||
}
|
||||
@@ -14,7 +14,12 @@ const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
const moduleRules = [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
use: {
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
exclude: path.resolve(__dirname, "node_modules"),
|
||||
},
|
||||
{
|
||||
@@ -62,7 +67,7 @@ const config = {
|
||||
modules: [path.resolve("node_modules")],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
filename: "[name].cjs",
|
||||
path: path.resolve(__dirname, "build-cli"),
|
||||
},
|
||||
module: { rules: moduleRules },
|
||||
|
||||
@@ -10,7 +10,12 @@ const common = {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: "ts-loader",
|
||||
use: {
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
},
|
||||
},
|
||||
exclude: /node_modules\/(?!(@bitwarden)\/).*/,
|
||||
},
|
||||
],
|
||||
@@ -57,6 +62,9 @@ const main = {
|
||||
],
|
||||
}),
|
||||
],
|
||||
output: {
|
||||
filename: "[name].cjs",
|
||||
},
|
||||
externals: {
|
||||
"electron-reload": "commonjs2 electron-reload",
|
||||
keytar: "commonjs2 keytar",
|
||||
|
||||
@@ -38,7 +38,7 @@ const common = {
|
||||
plugins: [],
|
||||
resolve: {
|
||||
extensions: [".tsx", ".ts", ".js", ".json"],
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.renderer.json" })],
|
||||
symlinks: false,
|
||||
modules: [path.resolve("node_modules")],
|
||||
},
|
||||
@@ -113,7 +113,7 @@ const renderer = {
|
||||
},
|
||||
plugins: [
|
||||
new AngularWebpackPlugin({
|
||||
tsConfigPath: "tsconfig.json",
|
||||
tsConfigPath: "tsconfig.renderer.json",
|
||||
entryModule: "src/app/app.module#AppModule",
|
||||
sourceMap: true,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user