mirror of
https://github.com/bitwarden/browser
synced 2026-02-07 04:03:29 +00:00
Merge branch 'main' of github.com:bitwarden/clients into PM-25525-DEBT-Fix-SystemServiceProvider-dependency-injection
pull latest from main to troubleshoot github action error
This commit is contained in:
13
apps/cli/CLAUDE.md
Normal file
13
apps/cli/CLAUDE.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# CLI - Critical Rules
|
||||
|
||||
- **ALWAYS** output structured JSON when `process.env.BW_RESPONSE === "true"`
|
||||
- Use Response objects (MessageResponse, ListResponse, etc.) from `/apps/cli/src/models/response/`
|
||||
- DON'T write free-form text that breaks JSON parsing
|
||||
|
||||
- **NEVER** use `console.log()` for output
|
||||
- Use `CliUtils.writeLn()` to respect `BW_QUIET` and `BW_RESPONSE` environment variables
|
||||
- Use the `ConsoleLogService` from the `ServiceContainer`
|
||||
|
||||
- **ALWAYS** respect `BW_CLEANEXIT` environment variable
|
||||
- Exit code 0 even on errors when `BW_CLEANEXIT` is set
|
||||
- Required for scripting environments that need clean exits
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@bitwarden/cli",
|
||||
"description": "A secure and free password manager for all of your devices.",
|
||||
"version": "2025.9.0",
|
||||
"version": "2025.10.0",
|
||||
"keywords": [
|
||||
"bitwarden",
|
||||
"password",
|
||||
|
||||
86
apps/cli/project.json
Normal file
86
apps/cli/project.json
Normal file
@@ -0,0 +1,86 @@
|
||||
{
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"name": "cli",
|
||||
"projectType": "application",
|
||||
"sourceRoot": "apps/cli/src",
|
||||
"tags": ["scope:cli", "type:app"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "oss-dev",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/cli",
|
||||
"webpackConfig": "apps/cli/webpack.config.js",
|
||||
"tsConfig": "apps/cli/tsconfig.json",
|
||||
"main": "apps/cli/src/bw.ts",
|
||||
"target": "node",
|
||||
"compiler": "tsc"
|
||||
},
|
||||
"configurations": {
|
||||
"oss": {
|
||||
"mode": "production",
|
||||
"outputPath": "dist/apps/cli/oss"
|
||||
},
|
||||
"oss-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/oss-dev"
|
||||
},
|
||||
"commercial": {
|
||||
"mode": "production",
|
||||
"outputPath": "dist/apps/cli/commercial",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
},
|
||||
"commercial-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/commercial-dev",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nx/webpack:webpack",
|
||||
"defaultConfiguration": "oss-dev",
|
||||
"options": {
|
||||
"outputPath": "dist/apps/cli",
|
||||
"webpackConfig": "apps/cli/webpack.config.js",
|
||||
"tsConfig": "apps/cli/tsconfig.json",
|
||||
"main": "apps/cli/src/bw.ts",
|
||||
"target": "node",
|
||||
"compiler": "tsc",
|
||||
"watch": true
|
||||
},
|
||||
"configurations": {
|
||||
"oss-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/oss-dev"
|
||||
},
|
||||
"commercial-dev": {
|
||||
"mode": "development",
|
||||
"outputPath": "dist/apps/cli/commercial-dev",
|
||||
"webpackConfig": "bitwarden_license/bit-cli/webpack.config.js",
|
||||
"main": "bitwarden_license/bit-cli/src/bw.ts",
|
||||
"tsConfig": "bitwarden_license/bit-cli/tsconfig.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "apps/cli/jest.config.js"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/cli/**/*.ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,7 @@ import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/a
|
||||
import { EncString } from "@bitwarden/common/key-management/crypto/models/enc-string";
|
||||
import { KeyConnectorService } from "@bitwarden/common/key-management/key-connector/abstractions/key-connector.service";
|
||||
import { MasterPasswordServiceAbstraction } from "@bitwarden/common/key-management/master-password/abstractions/master-password.service.abstraction";
|
||||
import { ErrorResponse } from "@bitwarden/common/models/response/error.response";
|
||||
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
|
||||
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
|
||||
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
|
||||
@@ -369,6 +370,15 @@ export class LoginCommand {
|
||||
|
||||
return await this.handleSuccessResponse(response);
|
||||
} catch (e) {
|
||||
if (
|
||||
e instanceof ErrorResponse &&
|
||||
e.message === "Username or password is incorrect. Try again."
|
||||
) {
|
||||
const env = await firstValueFrom(this.environmentService.environment$);
|
||||
const host = Utils.getHost(env.getWebVaultUrl());
|
||||
return Response.error(this.i18nService.t("invalidMasterPasswordConfirmEmailAndHost", host));
|
||||
}
|
||||
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// FIXME: Update this file to be type safe and remove this and next line
|
||||
// @ts-strict-ignore
|
||||
import * as inquirer from "inquirer";
|
||||
import { firstValueFrom, map, switchMap } from "rxjs";
|
||||
|
||||
import { UpdateCollectionRequest } from "@bitwarden/admin-console/common";
|
||||
@@ -9,6 +10,7 @@ import { PolicyType } from "@bitwarden/common/admin-console/enums";
|
||||
import { SelectionReadOnlyRequest } from "@bitwarden/common/admin-console/models/request/selection-read-only.request";
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { EncryptService } from "@bitwarden/common/key-management/crypto/abstractions/encrypt.service";
|
||||
import { CipherExport } from "@bitwarden/common/models/export/cipher.export";
|
||||
import { CollectionExport } from "@bitwarden/common/models/export/collection.export";
|
||||
@@ -40,6 +42,7 @@ export class EditCommand {
|
||||
private accountService: AccountService,
|
||||
private cliRestrictedItemTypesService: CliRestrictedItemTypesService,
|
||||
private policyService: PolicyService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async run(
|
||||
@@ -92,6 +95,10 @@ export class EditCommand {
|
||||
private async editCipher(id: string, req: CipherExport) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
const hasPremium = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(activeUserId),
|
||||
);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
@@ -102,6 +109,17 @@ export class EditCommand {
|
||||
}
|
||||
cipherView = CipherExport.toView(req, cipherView);
|
||||
|
||||
// When a user is editing an archived cipher and does not have premium, automatically unarchive it
|
||||
if (cipherView.isArchived && !hasPremium) {
|
||||
const acceptedPrompt = await this.promptForArchiveEdit();
|
||||
|
||||
if (!acceptedPrompt) {
|
||||
return Response.error("Edit cancelled.");
|
||||
}
|
||||
|
||||
cipherView.archivedDate = null;
|
||||
}
|
||||
|
||||
const isCipherRestricted =
|
||||
await this.cliRestrictedItemTypesService.isCipherRestricted(cipherView);
|
||||
if (isCipherRestricted) {
|
||||
@@ -240,6 +258,38 @@ export class EditCommand {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Prompt the user to accept movement of their cipher back to the their vault. */
|
||||
private async promptForArchiveEdit(): Promise<boolean> {
|
||||
// When running in serve or no interaction mode, automatically accept the prompt
|
||||
if (process.env.BW_SERVE === "true" || process.env.BW_NOINTERACTION === "true") {
|
||||
CliUtils.writeLn(
|
||||
"Archive is only available with a Premium subscription, which has ended. Your edit was saved and the item was moved back to your vault.",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const answer: inquirer.Answers = await inquirer.createPromptModule({
|
||||
output: process.stderr,
|
||||
})({
|
||||
type: "list",
|
||||
name: "confirm",
|
||||
message:
|
||||
"When you edit and save details for an archived item without a Premium subscription, it'll be moved from your archive back to your vault.",
|
||||
choices: [
|
||||
{
|
||||
name: "Move now",
|
||||
value: "confirmed",
|
||||
},
|
||||
{
|
||||
name: "Cancel",
|
||||
value: "cancel",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return answer.confirm === "confirmed";
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { EventType } from "@bitwarden/common/enums";
|
||||
import { ListResponse as ApiListResponse } from "@bitwarden/common/models/response/list.response";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { SearchService } from "@bitwarden/common/vault/abstractions/search.service";
|
||||
@@ -45,6 +46,7 @@ export class ListCommand {
|
||||
private accountService: AccountService,
|
||||
private keyService: KeyService,
|
||||
private cliRestrictedItemTypesService: CliRestrictedItemTypesService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
) {}
|
||||
|
||||
async run(object: string, cmdOptions: Record<string, any>): Promise<Response> {
|
||||
@@ -71,8 +73,13 @@ export class ListCommand {
|
||||
let ciphers: CipherView[];
|
||||
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const userCanArchive = await firstValueFrom(
|
||||
this.cipherArchiveService.userCanArchive$(activeUserId),
|
||||
);
|
||||
|
||||
options.trash = options.trash || false;
|
||||
options.archived = userCanArchive && options.archived;
|
||||
|
||||
if (options.url != null && options.url.trim() !== "") {
|
||||
ciphers = await this.cipherService.getAllDecryptedForUrl(options.url, activeUserId);
|
||||
} else {
|
||||
@@ -85,9 +92,12 @@ export class ListCommand {
|
||||
options.organizationId != null
|
||||
) {
|
||||
ciphers = ciphers.filter((c) => {
|
||||
if (options.trash !== c.isDeleted) {
|
||||
const matchesStateOptions = this.matchesStateOptions(c, options);
|
||||
|
||||
if (!matchesStateOptions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options.folderId != null) {
|
||||
if (options.folderId === "notnull" && c.folderId != null) {
|
||||
return true;
|
||||
@@ -131,11 +141,16 @@ export class ListCommand {
|
||||
return false;
|
||||
});
|
||||
} else if (options.search == null || options.search.trim() === "") {
|
||||
ciphers = ciphers.filter((c) => options.trash === c.isDeleted);
|
||||
ciphers = ciphers.filter((c) => this.matchesStateOptions(c, options));
|
||||
}
|
||||
|
||||
if (options.search != null && options.search.trim() !== "") {
|
||||
ciphers = this.searchService.searchCiphersBasic(ciphers, options.search, options.trash);
|
||||
ciphers = this.searchService.searchCiphersBasic(
|
||||
ciphers,
|
||||
options.search,
|
||||
options.trash,
|
||||
options.archived,
|
||||
);
|
||||
}
|
||||
|
||||
ciphers = await this.cliRestrictedItemTypesService.filterRestrictedCiphers(ciphers);
|
||||
@@ -287,6 +302,17 @@ export class ListCommand {
|
||||
const res = new ListResponse(organizations.map((o) => new OrganizationResponse(o)));
|
||||
return Response.success(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the cipher passes the state filter options.
|
||||
* @returns true if the cipher matches the requested state
|
||||
*/
|
||||
private matchesStateOptions(c: CipherView, options: Options): boolean {
|
||||
const passesTrashFilter = options.trash === c.isDeleted;
|
||||
const passesArchivedFilter = options.archived === c.isArchived;
|
||||
|
||||
return passesTrashFilter && passesArchivedFilter;
|
||||
}
|
||||
}
|
||||
|
||||
class Options {
|
||||
@@ -296,6 +322,7 @@ class Options {
|
||||
search: string;
|
||||
url: string;
|
||||
trash: boolean;
|
||||
archived: boolean;
|
||||
|
||||
constructor(passedOptions: Record<string, any>) {
|
||||
this.organizationId = passedOptions?.organizationid || passedOptions?.organizationId;
|
||||
@@ -304,5 +331,6 @@ class Options {
|
||||
this.search = passedOptions?.search;
|
||||
this.url = passedOptions?.url;
|
||||
this.trash = CliUtils.convertBooleanOption(passedOptions?.trash);
|
||||
this.archived = CliUtils.convertBooleanOption(passedOptions?.archived);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,14 @@ import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { Cipher } from "@bitwarden/common/vault/models/domain/cipher";
|
||||
import { CipherAuthorizationService } from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
|
||||
@@ -12,6 +18,8 @@ export class RestoreCommand {
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
private cipherAuthorizationService: CipherAuthorizationService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
private configService: ConfigService,
|
||||
) {}
|
||||
|
||||
async run(object: string, id: string): Promise<Response> {
|
||||
@@ -30,10 +38,23 @@ export class RestoreCommand {
|
||||
private async restoreCipher(id: string) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(id, activeUserId);
|
||||
const isArchivedVaultEnabled = await firstValueFrom(
|
||||
this.configService.getFeatureFlag$(FeatureFlag.PM19148_InnovationArchive),
|
||||
);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
if (cipher.archivedDate && isArchivedVaultEnabled) {
|
||||
return this.restoreArchivedCipher(cipher, activeUserId);
|
||||
} else {
|
||||
return this.restoreDeletedCipher(cipher, activeUserId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Restores a cipher from the trash. */
|
||||
private async restoreDeletedCipher(cipher: Cipher, userId: UserId) {
|
||||
if (cipher.deletedDate == null) {
|
||||
return Response.badRequest("Cipher is not in trash.");
|
||||
}
|
||||
@@ -47,7 +68,17 @@ export class RestoreCommand {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherService.restoreWithServer(id, activeUserId);
|
||||
await this.cipherService.restoreWithServer(cipher.id, userId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Restore a cipher from the archive vault */
|
||||
private async restoreArchivedCipher(cipher: Cipher, userId: UserId) {
|
||||
try {
|
||||
await this.cipherArchiveService.unarchiveWithServer(cipher.id as CipherId, userId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
|
||||
@@ -51,7 +51,7 @@ export class ServeCommand {
|
||||
.use(koaBodyParser())
|
||||
.use(koaJson({ pretty: false, param: "pretty" }));
|
||||
|
||||
this.serveConfigurator.configureRouter(router);
|
||||
await this.serveConfigurator.configureRouter(router);
|
||||
|
||||
server.use(router.routes()).use(router.allowedMethods());
|
||||
|
||||
|
||||
@@ -41,6 +41,15 @@
|
||||
"invalidMasterPassword": {
|
||||
"message": "Invalid master password."
|
||||
},
|
||||
"invalidMasterPasswordConfirmEmailAndHost": {
|
||||
"message": "Invalid master password. Confirm your email is correct and your account was created on $HOST$.",
|
||||
"placeholders": {
|
||||
"host": {
|
||||
"content": "$1",
|
||||
"example": "vault.bitwarden.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sessionTimeout": {
|
||||
"message": "Your session has timed out. Please go back and try logging in again."
|
||||
},
|
||||
|
||||
@@ -5,6 +5,8 @@ import * as koaRouter from "@koa/router";
|
||||
import * as koa from "koa";
|
||||
import { firstValueFrom, map } from "rxjs";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||
import { LockCommand } from "./auth/commands/lock.command";
|
||||
@@ -26,6 +28,7 @@ import {
|
||||
SendListCommand,
|
||||
SendRemovePasswordCommand,
|
||||
} from "./tools/send";
|
||||
import { ArchiveCommand } from "./vault/archive.command";
|
||||
import { CreateCommand } from "./vault/create.command";
|
||||
import { DeleteCommand } from "./vault/delete.command";
|
||||
import { SyncCommand } from "./vault/sync.command";
|
||||
@@ -40,6 +43,7 @@ export class OssServeConfigurator {
|
||||
private statusCommand: StatusCommand;
|
||||
private syncCommand: SyncCommand;
|
||||
private deleteCommand: DeleteCommand;
|
||||
private archiveCommand: ArchiveCommand;
|
||||
private confirmCommand: ConfirmCommand;
|
||||
private restoreCommand: RestoreCommand;
|
||||
private lockCommand: LockCommand;
|
||||
@@ -81,6 +85,7 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.keyService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
);
|
||||
this.createCommand = new CreateCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
@@ -104,6 +109,7 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.policyService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
this.generateCommand = new GenerateCommand(
|
||||
this.serviceContainer.passwordGenerationService,
|
||||
@@ -127,6 +133,13 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
);
|
||||
this.archiveCommand = new ArchiveCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
this.confirmCommand = new ConfirmCommand(
|
||||
this.serviceContainer.apiService,
|
||||
this.serviceContainer.keyService,
|
||||
@@ -140,6 +153,8 @@ export class OssServeConfigurator {
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.configService,
|
||||
);
|
||||
this.shareCommand = new ShareCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
@@ -199,7 +214,7 @@ export class OssServeConfigurator {
|
||||
);
|
||||
}
|
||||
|
||||
configureRouter(router: koaRouter) {
|
||||
async configureRouter(router: koaRouter) {
|
||||
router.get("/generate", async (ctx, next) => {
|
||||
const response = await this.generateCommand.run(ctx.request.query);
|
||||
this.processResponse(ctx.response, response);
|
||||
@@ -401,6 +416,23 @@ export class OssServeConfigurator {
|
||||
this.processResponse(ctx.response, response);
|
||||
await next();
|
||||
});
|
||||
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
router.post("/archive/:object/:id", async (ctx, next) => {
|
||||
if (await this.errorIfLocked(ctx.response)) {
|
||||
await next();
|
||||
return;
|
||||
}
|
||||
let response: Response = null;
|
||||
response = await this.archiveCommand.run(ctx.params.object, ctx.params.id);
|
||||
this.processResponse(ctx.response, response);
|
||||
await next();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected processResponse(res: koa.Response, commandResponse: Response) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import { program, Command, OptionValues } from "commander";
|
||||
import { firstValueFrom, of, switchMap } from "rxjs";
|
||||
|
||||
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { LockCommand } from "./auth/commands/lock.command";
|
||||
import { LoginCommand } from "./auth/commands/login.command";
|
||||
@@ -26,6 +27,10 @@ const writeLn = CliUtils.writeLn;
|
||||
|
||||
export class Program extends BaseProgram {
|
||||
async register() {
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
program
|
||||
.option("--pretty", "Format output. JSON is tabbed with two spaces.")
|
||||
.option("--raw", "Return raw output instead of a descriptive message.")
|
||||
@@ -94,6 +99,9 @@ export class Program extends BaseProgram {
|
||||
" bw edit folder c7c7b60b-9c61-40f2-8ccd-36c49595ed72 eyJuYW1lIjoiTXkgRm9sZGVyMiJ9Cg==",
|
||||
);
|
||||
writeLn(" bw delete item 99ee88d2-6046-4ea7-92c2-acac464b1412");
|
||||
if (isArchivedEnabled) {
|
||||
writeLn(" bw archive item 99ee88d2-6046-4ea7-92c2-acac464b1412");
|
||||
}
|
||||
writeLn(" bw generate -lusn --length 18");
|
||||
writeLn(" bw config server https://bitwarden.example.com");
|
||||
writeLn(" bw send -f ./file.ext");
|
||||
|
||||
@@ -15,7 +15,7 @@ export async function registerOssPrograms(serviceContainer: ServiceContainer) {
|
||||
await program.register();
|
||||
|
||||
const vaultProgram = new VaultProgram(serviceContainer);
|
||||
vaultProgram.register();
|
||||
await vaultProgram.register();
|
||||
|
||||
const sendProgram = new SendProgram(serviceContainer);
|
||||
sendProgram.register();
|
||||
|
||||
@@ -123,6 +123,7 @@ import { SendApiService } from "@bitwarden/common/tools/send/services/send-api.s
|
||||
import { SendStateProvider } from "@bitwarden/common/tools/send/services/send-state.provider";
|
||||
import { SendService } from "@bitwarden/common/tools/send/services/send.service";
|
||||
import { UserId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherEncryptionService } from "@bitwarden/common/vault/abstractions/cipher-encryption.service";
|
||||
import { InternalFolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import {
|
||||
@@ -130,6 +131,7 @@ import {
|
||||
DefaultCipherAuthorizationService,
|
||||
} from "@bitwarden/common/vault/services/cipher-authorization.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/services/cipher.service";
|
||||
import { DefaultCipherArchiveService } from "@bitwarden/common/vault/services/default-cipher-archive.service";
|
||||
import { DefaultCipherEncryptionService } from "@bitwarden/common/vault/services/default-cipher-encryption.service";
|
||||
import { CipherFileUploadService } from "@bitwarden/common/vault/services/file-upload/cipher-file-upload.service";
|
||||
import { FolderApiService } from "@bitwarden/common/vault/services/folder/folder-api.service";
|
||||
@@ -302,6 +304,7 @@ export class ServiceContainer {
|
||||
cipherEncryptionService: CipherEncryptionService;
|
||||
restrictedItemTypesService: RestrictedItemTypesService;
|
||||
cliRestrictedItemTypesService: CliRestrictedItemTypesService;
|
||||
cipherArchiveService: CipherArchiveService;
|
||||
|
||||
constructor() {
|
||||
let p = null;
|
||||
@@ -729,6 +732,13 @@ export class ServiceContainer {
|
||||
this.messagingService,
|
||||
);
|
||||
|
||||
this.cipherArchiveService = new DefaultCipherArchiveService(
|
||||
this.cipherService,
|
||||
this.apiService,
|
||||
this.billingAccountProfileStateService,
|
||||
this.configService,
|
||||
);
|
||||
|
||||
this.folderService = new FolderService(
|
||||
this.keyService,
|
||||
this.encryptService,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// @ts-strict-ignore
|
||||
import { program, Command } from "commander";
|
||||
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
|
||||
import { ConfirmCommand } from "./admin-console/commands/confirm.command";
|
||||
import { ShareCommand } from "./admin-console/commands/share.command";
|
||||
import { BaseProgram } from "./base-program";
|
||||
@@ -13,25 +15,34 @@ import { Response } from "./models/response";
|
||||
import { ExportCommand } from "./tools/export.command";
|
||||
import { ImportCommand } from "./tools/import.command";
|
||||
import { CliUtils } from "./utils";
|
||||
import { ArchiveCommand } from "./vault/archive.command";
|
||||
import { CreateCommand } from "./vault/create.command";
|
||||
import { DeleteCommand } from "./vault/delete.command";
|
||||
|
||||
const writeLn = CliUtils.writeLn;
|
||||
|
||||
export class VaultProgram extends BaseProgram {
|
||||
register() {
|
||||
async register() {
|
||||
const isArchivedEnabled = await this.serviceContainer.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
program
|
||||
.addCommand(this.listCommand())
|
||||
.addCommand(this.listCommand(isArchivedEnabled))
|
||||
.addCommand(this.getCommand())
|
||||
.addCommand(this.createCommand())
|
||||
.addCommand(this.editCommand())
|
||||
.addCommand(this.deleteCommand())
|
||||
.addCommand(this.restoreCommand())
|
||||
.addCommand(this.restoreCommand(isArchivedEnabled))
|
||||
.addCommand(this.shareCommand("move", false))
|
||||
.addCommand(this.confirmCommand())
|
||||
.addCommand(this.importCommand())
|
||||
.addCommand(this.exportCommand())
|
||||
.addCommand(this.shareCommand("share", true));
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
program.addCommand(this.archiveCommand());
|
||||
}
|
||||
}
|
||||
|
||||
private validateObject(requestedObject: string, validObjects: string[]): boolean {
|
||||
@@ -42,7 +53,7 @@ export class VaultProgram extends BaseProgram {
|
||||
Response.badRequest(
|
||||
'Unknown object "' +
|
||||
requestedObject +
|
||||
'". Allowed objects are ' +
|
||||
'". Allowed objects are: ' +
|
||||
validObjects.join(", ") +
|
||||
".",
|
||||
),
|
||||
@@ -51,7 +62,7 @@ export class VaultProgram extends BaseProgram {
|
||||
return success;
|
||||
}
|
||||
|
||||
private listCommand(): Command {
|
||||
private listCommand(isArchivedEnabled: boolean): Command {
|
||||
const listObjects = [
|
||||
"items",
|
||||
"folders",
|
||||
@@ -61,7 +72,7 @@ export class VaultProgram extends BaseProgram {
|
||||
"organizations",
|
||||
];
|
||||
|
||||
return new Command("list")
|
||||
const command = new Command("list")
|
||||
.argument("<object>", "Valid objects are: " + listObjects.join(", "))
|
||||
.description("List an array of objects from the vault.")
|
||||
.option("--search <search>", "Perform a search on the listed objects.")
|
||||
@@ -94,6 +105,9 @@ export class VaultProgram extends BaseProgram {
|
||||
" bw list items --folderid 60556c31-e649-4b5d-8daf-fc1c391a1bf2 --organizationid notnull",
|
||||
);
|
||||
writeLn(" bw list items --trash");
|
||||
if (isArchivedEnabled) {
|
||||
writeLn(" bw list items --archived");
|
||||
}
|
||||
writeLn(" bw list folders --search email");
|
||||
writeLn(" bw list org-members --organizationid 60556c31-e649-4b5d-8daf-fc1c391a1bf2");
|
||||
writeLn("", true);
|
||||
@@ -116,11 +130,18 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.keyService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
);
|
||||
const response = await command.run(object, cmd);
|
||||
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
command.option("--archived", "Filter items that are archived.");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private getCommand(): Command {
|
||||
@@ -286,6 +307,7 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cliRestrictedItemTypesService,
|
||||
this.serviceContainer.policyService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
const response = await command.run(object, id, encodedJson, cmd);
|
||||
this.processResponse(response);
|
||||
@@ -336,12 +358,41 @@ export class VaultProgram extends BaseProgram {
|
||||
});
|
||||
}
|
||||
|
||||
private restoreCommand(): Command {
|
||||
private archiveCommand(): Command {
|
||||
const archiveObjects = ["item"];
|
||||
return new Command("archive")
|
||||
.argument("<object>", "Valid objects are: " + archiveObjects.join(", "))
|
||||
.argument("<id>", "Object's globally unique `id`.")
|
||||
.description("Archive an object from the vault.")
|
||||
.on("--help", () => {
|
||||
writeLn("\n Examples:");
|
||||
writeLn("");
|
||||
writeLn(" bw archive item 7063feab-4b10-472e-b64c-785e2b870b92");
|
||||
writeLn("", true);
|
||||
})
|
||||
.action(async (object, id) => {
|
||||
if (!this.validateObject(object, archiveObjects)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.exitIfLocked();
|
||||
const command = new ArchiveCommand(
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.configService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.billingAccountProfileStateService,
|
||||
);
|
||||
const response = await command.run(object, id);
|
||||
this.processResponse(response);
|
||||
});
|
||||
}
|
||||
|
||||
private restoreCommand(isArchivedEnabled: boolean): Command {
|
||||
const restoreObjects = ["item"];
|
||||
return new Command("restore")
|
||||
const command = new Command("restore")
|
||||
.argument("<object>", "Valid objects are: " + restoreObjects.join(", "))
|
||||
.argument("<id>", "Object's globally unique `id`.")
|
||||
.description("Restores an object from the trash.")
|
||||
.on("--help", () => {
|
||||
writeLn("\n Examples:");
|
||||
writeLn("");
|
||||
@@ -358,10 +409,20 @@ export class VaultProgram extends BaseProgram {
|
||||
this.serviceContainer.cipherService,
|
||||
this.serviceContainer.accountService,
|
||||
this.serviceContainer.cipherAuthorizationService,
|
||||
this.serviceContainer.cipherArchiveService,
|
||||
this.serviceContainer.configService,
|
||||
);
|
||||
const response = await command.run(object, id);
|
||||
this.processResponse(response);
|
||||
});
|
||||
|
||||
if (isArchivedEnabled) {
|
||||
command.description("Restores an object from the trash or archive.");
|
||||
} else {
|
||||
command.description("Restores an object from the trash.");
|
||||
}
|
||||
|
||||
return command;
|
||||
}
|
||||
|
||||
private shareCommand(commandName: string, deprecated: boolean): Command {
|
||||
|
||||
109
apps/cli/src/vault/archive.command.ts
Normal file
109
apps/cli/src/vault/archive.command.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import { firstValueFrom } from "rxjs";
|
||||
|
||||
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
|
||||
import { getUserId } from "@bitwarden/common/auth/services/account.service";
|
||||
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
|
||||
import { FeatureFlag } from "@bitwarden/common/enums/feature-flag.enum";
|
||||
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
|
||||
import { CipherId } from "@bitwarden/common/types/guid";
|
||||
import { CipherArchiveService } from "@bitwarden/common/vault/abstractions/cipher-archive.service";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
|
||||
import { CipherViewLikeUtils } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
|
||||
import { UserId } from "@bitwarden/user-core";
|
||||
|
||||
import { Response } from "../models/response";
|
||||
|
||||
export class ArchiveCommand {
|
||||
constructor(
|
||||
private cipherService: CipherService,
|
||||
private accountService: AccountService,
|
||||
private configService: ConfigService,
|
||||
private cipherArchiveService: CipherArchiveService,
|
||||
private billingAccountProfileStateService: BillingAccountProfileStateService,
|
||||
) {}
|
||||
|
||||
async run(object: string, id: string): Promise<Response> {
|
||||
const featureFlagEnabled = await this.configService.getFeatureFlag(
|
||||
FeatureFlag.PM19148_InnovationArchive,
|
||||
);
|
||||
|
||||
if (!featureFlagEnabled) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
if (id != null) {
|
||||
id = id.toLowerCase();
|
||||
}
|
||||
|
||||
const normalizedObject = object.toLowerCase();
|
||||
|
||||
if (normalizedObject === "item") {
|
||||
return this.archiveCipher(id);
|
||||
}
|
||||
|
||||
return Response.badRequest("Unknown object.");
|
||||
}
|
||||
|
||||
private async archiveCipher(cipherId: string) {
|
||||
const activeUserId = await firstValueFrom(this.accountService.activeAccount$.pipe(getUserId));
|
||||
const cipher = await this.cipherService.get(cipherId, activeUserId);
|
||||
|
||||
if (cipher == null) {
|
||||
return Response.notFound();
|
||||
}
|
||||
|
||||
const cipherView = await this.cipherService.decrypt(cipher, activeUserId);
|
||||
|
||||
const { canArchive, errorMessage } = await this.userCanArchiveCipher(cipherView, activeUserId);
|
||||
|
||||
if (!canArchive) {
|
||||
return Response.error(errorMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.cipherArchiveService.archiveWithServer(cipherView.id as CipherId, activeUserId);
|
||||
return Response.success();
|
||||
} catch (e) {
|
||||
return Response.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the user can archive the given cipher.
|
||||
* When the user cannot archive the cipher, an appropriate error message is provided.
|
||||
*/
|
||||
private async userCanArchiveCipher(
|
||||
cipher: CipherView,
|
||||
userId: UserId,
|
||||
): Promise<
|
||||
{ canArchive: true; errorMessage?: never } | { canArchive: false; errorMessage: string }
|
||||
> {
|
||||
const hasPremiumFromAnySource = await firstValueFrom(
|
||||
this.billingAccountProfileStateService.hasPremiumFromAnySource$(userId),
|
||||
);
|
||||
|
||||
switch (true) {
|
||||
case !hasPremiumFromAnySource: {
|
||||
return {
|
||||
canArchive: false,
|
||||
errorMessage: "Premium status is required to use this feature.",
|
||||
};
|
||||
}
|
||||
case CipherViewLikeUtils.isArchived(cipher): {
|
||||
return { canArchive: false, errorMessage: "Item is already archived." };
|
||||
}
|
||||
case CipherViewLikeUtils.isDeleted(cipher): {
|
||||
return {
|
||||
canArchive: false,
|
||||
errorMessage: "Item is in the trash, the item must be restored before archiving.",
|
||||
};
|
||||
}
|
||||
case cipher.organizationId != null: {
|
||||
return { canArchive: false, errorMessage: "Cannot archive items in an organization." };
|
||||
}
|
||||
default:
|
||||
return { canArchive: true };
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,6 +20,7 @@ import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
|
||||
import { FolderApiServiceAbstraction } from "@bitwarden/common/vault/abstractions/folder/folder-api.service.abstraction";
|
||||
import { FolderService } from "@bitwarden/common/vault/abstractions/folder/folder.service.abstraction";
|
||||
import { Folder } from "@bitwarden/common/vault/models/domain/folder";
|
||||
import { KeyService } from "@bitwarden/key-management";
|
||||
|
||||
import { OrganizationCollectionRequest } from "../admin-console/models/request/organization-collection.request";
|
||||
@@ -183,8 +184,8 @@ export class CreateCommand {
|
||||
const userKey = await this.keyService.getUserKey(activeUserId);
|
||||
const folder = await this.folderService.encrypt(FolderExport.toView(req), userKey);
|
||||
try {
|
||||
await this.folderApiService.save(folder, activeUserId);
|
||||
const newFolder = await this.folderService.get(folder.id, activeUserId);
|
||||
const folderData = await this.folderApiService.save(folder, activeUserId);
|
||||
const newFolder = new Folder(folderData);
|
||||
const decFolder = await newFolder.decrypt();
|
||||
const res = new FolderResponse(decFolder);
|
||||
return Response.success(res);
|
||||
|
||||
124
apps/cli/webpack.base.js
Normal file
124
apps/cli/webpack.base.js
Normal file
@@ -0,0 +1,124 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const nodeExternals = require("webpack-node-externals");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const config = require("./config/config");
|
||||
|
||||
module.exports.getEnv = function getEnv() {
|
||||
const ENV = process.env.NODE_ENV == null ? "development" : process.env.NODE_ENV;
|
||||
return { ENV };
|
||||
};
|
||||
|
||||
const DEFAULT_PARAMS = {
|
||||
localesPath: "./src/locales",
|
||||
modulesPath: [path.resolve("../../node_modules")],
|
||||
externalsModulesDir: "../../node_modules",
|
||||
outputPath: path.resolve(__dirname, "build"),
|
||||
watch: false,
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {{
|
||||
* configName: string;
|
||||
* entry: string;
|
||||
* tsConfig: string;
|
||||
* outputPath?: string;
|
||||
* mode?: string;
|
||||
* env?: string;
|
||||
* modulesPath?: string[];
|
||||
* localesPath?: string;
|
||||
* externalsModulesDir?: string;
|
||||
* watch?: boolean;
|
||||
* }} params
|
||||
*/
|
||||
module.exports.buildConfig = function buildConfig(params) {
|
||||
params = { ...DEFAULT_PARAMS, ...params };
|
||||
const ENV = params.env || module.exports.getEnv().ENV;
|
||||
|
||||
const envConfig = config.load(ENV);
|
||||
config.log(`Building CLI - ${params.configName} version`);
|
||||
config.log(envConfig);
|
||||
|
||||
const moduleRules = [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
exclude: path.resolve(__dirname, "node_modules"),
|
||||
},
|
||||
];
|
||||
|
||||
const plugins = [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{ from: params.localesPath, to: "locales" }],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.BWCLI_ENV": JSON.stringify(ENV),
|
||||
}),
|
||||
new webpack.BannerPlugin({
|
||||
banner: "#!/usr/bin/env node",
|
||||
raw: true,
|
||||
}),
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /^encoding$/,
|
||||
contextRegExp: /node-fetch/,
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
ENV: ENV,
|
||||
BWCLI_ENV: ENV,
|
||||
FLAGS: envConfig.flags,
|
||||
DEV_FLAGS: envConfig.devFlags,
|
||||
}),
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /canvas/,
|
||||
contextRegExp: /jsdom$/,
|
||||
}),
|
||||
];
|
||||
|
||||
const webpackConfig = {
|
||||
mode: params.mode || ENV,
|
||||
target: "node",
|
||||
devtool: ENV === "development" ? "eval-source-map" : "source-map",
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
entry: {
|
||||
bw: params.entry,
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
symlinks: false,
|
||||
modules: params.modulesPath,
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: params.tsConfig })],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
path: path.resolve(params.outputPath),
|
||||
clean: true,
|
||||
},
|
||||
module: { rules: moduleRules },
|
||||
plugins: plugins,
|
||||
externals: [
|
||||
nodeExternals({
|
||||
modulesDir: params.externalsModulesDir,
|
||||
allowlist: [/@bitwarden/],
|
||||
}),
|
||||
],
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
};
|
||||
if (params.watch) {
|
||||
webpackConfig.watch = true;
|
||||
webpackConfig.watchOptions = {
|
||||
ignored: /node_modules/,
|
||||
poll: 1000,
|
||||
};
|
||||
}
|
||||
return webpackConfig;
|
||||
};
|
||||
@@ -1,89 +1,48 @@
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const nodeExternals = require("webpack-node-externals");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const config = require("./config/config");
|
||||
const { buildConfig } = require("./webpack.base");
|
||||
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = "development";
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
module.exports = (webpackConfig, context) => {
|
||||
// Detect if called by Nx (context parameter exists)
|
||||
const isNxBuild = context && context.options;
|
||||
|
||||
const envConfig = config.load(ENV);
|
||||
config.log(envConfig);
|
||||
if (isNxBuild) {
|
||||
// Nx build configuration
|
||||
const mode = context.options.mode || "development";
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = mode;
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
|
||||
const moduleRules = [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
use: "ts-loader",
|
||||
exclude: path.resolve(__dirname, "node_modules"),
|
||||
},
|
||||
];
|
||||
return buildConfig({
|
||||
configName: "OSS",
|
||||
entry: context.options.main || "apps/cli/src/bw.ts",
|
||||
tsConfig: "tsconfig.base.json",
|
||||
outputPath: path.resolve(context.context.root, context.options.outputPath),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("node_modules")],
|
||||
localesPath: "apps/cli/src/locales",
|
||||
externalsModulesDir: "node_modules",
|
||||
watch: context.options.watch || false,
|
||||
});
|
||||
} else {
|
||||
// npm build configuration
|
||||
if (process.env.NODE_ENV == null) {
|
||||
process.env.NODE_ENV = "development";
|
||||
}
|
||||
const ENV = (process.env.ENV = process.env.NODE_ENV);
|
||||
const mode = ENV;
|
||||
|
||||
const plugins = [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [{ from: "./src/locales", to: "locales" }],
|
||||
}),
|
||||
new webpack.DefinePlugin({
|
||||
"process.env.BWCLI_ENV": JSON.stringify(ENV),
|
||||
}),
|
||||
new webpack.BannerPlugin({
|
||||
banner: "#!/usr/bin/env node",
|
||||
raw: true,
|
||||
}),
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /^encoding$/,
|
||||
contextRegExp: /node-fetch/,
|
||||
}),
|
||||
new webpack.EnvironmentPlugin({
|
||||
ENV: ENV,
|
||||
BWCLI_ENV: ENV,
|
||||
FLAGS: envConfig.flags,
|
||||
DEV_FLAGS: envConfig.devFlags,
|
||||
}),
|
||||
new webpack.IgnorePlugin({
|
||||
resourceRegExp: /canvas/,
|
||||
contextRegExp: /jsdom$/,
|
||||
}),
|
||||
];
|
||||
|
||||
const webpackConfig = {
|
||||
mode: ENV,
|
||||
target: "node",
|
||||
devtool: ENV === "development" ? "eval-source-map" : "source-map",
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
entry: {
|
||||
bw: "./src/bw.ts",
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
symlinks: false,
|
||||
modules: [path.resolve("../../node_modules")],
|
||||
plugins: [new TsconfigPathsPlugin({ configFile: "./tsconfig.json" })],
|
||||
},
|
||||
output: {
|
||||
filename: "[name].js",
|
||||
path: path.resolve(__dirname, "build"),
|
||||
clean: true,
|
||||
},
|
||||
module: { rules: moduleRules },
|
||||
plugins: plugins,
|
||||
externals: [
|
||||
nodeExternals({
|
||||
modulesDir: "../../node_modules",
|
||||
allowlist: [/@bitwarden/],
|
||||
}),
|
||||
],
|
||||
experiments: {
|
||||
asyncWebAssembly: true,
|
||||
},
|
||||
return buildConfig({
|
||||
configName: "OSS",
|
||||
entry: "./src/bw.ts",
|
||||
tsConfig: "./tsconfig.json",
|
||||
outputPath: path.resolve(__dirname, "build"),
|
||||
mode: mode,
|
||||
env: ENV,
|
||||
modulesPath: [path.resolve("../../node_modules")],
|
||||
localesPath: "./src/locales",
|
||||
externalsModulesDir: "../../node_modules",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = webpackConfig;
|
||||
|
||||
Reference in New Issue
Block a user