Skip to content

Commit ce732e0

Browse files
Merge branch 'auth/pm-26551/mjml-build-script' into auth/pm-25240/update-otp-email-mjml
2 parents 9987de3 + d50eb5d commit ce732e0

File tree

26 files changed

+579
-935
lines changed

26 files changed

+579
-935
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,7 @@ jobs:
484484
uses: bitwarden/gh-actions/azure-logout@main
485485

486486
- name: Trigger self-host build
487-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
487+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
488488
with:
489489
github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
490490
script: |
@@ -525,7 +525,7 @@ jobs:
525525
uses: bitwarden/gh-actions/azure-logout@main
526526

527527
- name: Trigger k8s deploy
528-
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
528+
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
529529
with:
530530
github-token: ${{ steps.retrieve-secret-pat.outputs.github-pat-bitwarden-devops-bot-repo-scope }}
531531
script: |

bitwarden_license/src/Sso/Sso.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<!-- This is a transitive dependency to Sustainsys.Saml2.AspNetCore2 -->
1111
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
1212

13-
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.10.0" />
13+
<PackageReference Include="Sustainsys.Saml2.AspNetCore2" Version="2.11.0" />
1414
</ItemGroup>
1515

1616
<ItemGroup>

dev/docker-compose.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ services:
5353
- ./.data/postgres/log:/var/log/postgresql
5454
profiles:
5555
- postgres
56+
- ef
5657

5758
mysql:
5859
image: mysql:8.0
@@ -69,20 +70,21 @@ services:
6970
- mysql_dev_data:/var/lib/mysql
7071
profiles:
7172
- mysql
73+
- ef
7274

7375
mariadb:
7476
image: mariadb:10
7577
ports:
7678
- 4306:3306
7779
environment:
7880
MARIADB_USER: maria
79-
MARIADB_PASSWORD: ${MARIADB_ROOT_PASSWORD}
8081
MARIADB_DATABASE: vault_dev
8182
MARIADB_RANDOM_ROOT_PASSWORD: "true"
8283
volumes:
8384
- mariadb_dev_data:/var/lib/mysql
8485
profiles:
8586
- mariadb
87+
- ef
8688

8789
idp:
8890
image: kenchan0130/simplesamlphp:1.19.8
@@ -153,5 +155,6 @@ volumes:
153155
mssql_dev_data:
154156
postgres_dev_data:
155157
mysql_dev_data:
158+
mariadb_dev_data:
156159
rabbitmq_data:
157160
redis_data:

dev/migrate.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Foreach ($item in @(
7070
@($mysql, "MySQL", "MySqlMigrations", "mySql", 2),
7171
# MariaDB shares the MySQL connection string in the server config so they are mutually exclusive in that context.
7272
# However they can still be run independently for integration tests.
73-
@($mariadb, "MariaDB", "MySqlMigrations", "mySql", 3)
73+
@($mariadb, "MariaDB", "MySqlMigrations", "mySql", 4)
7474
)) {
7575
if (!$item[0] -and !$all) {
7676
continue

src/Api/Billing/Controllers/VNext/AccountBillingVNextController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public async Task<IResult> UpdatePaymentMethodAsync(
6666
}
6767

6868
[HttpPost("subscription")]
69-
[RequireFeature(FeatureFlagKeys.PM23385_UseNewPremiumFlow)]
69+
[RequireFeature(FeatureFlagKeys.PM24996ImplementUpgradeFromFreeDialog)]
7070
[InjectUser]
7171
public async Task<IResult> CreateSubscriptionAsync(
7272
[BindNever] User user,

src/Api/Billing/Controllers/VNext/SelfHostedAccountBillingController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public class SelfHostedAccountBillingController(
2121
ICreatePremiumSelfHostedSubscriptionCommand createPremiumSelfHostedSubscriptionCommand) : BaseBillingController
2222
{
2323
[HttpPost("license")]
24-
[RequireFeature(FeatureFlagKeys.PM23385_UseNewPremiumFlow)]
24+
[RequireFeature(FeatureFlagKeys.PM24996ImplementUpgradeFromFreeDialog)]
2525
[InjectUser]
2626
public async Task<IResult> UploadLicenseAsync(
2727
[BindNever] User user,

src/Core/Constants.cs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,12 @@ public static class AuthenticationSchemes
135135
public static class FeatureFlagKeys
136136
{
137137
/* Admin Console Team */
138-
public const string VerifiedSsoDomainEndpoint = "pm-12337-refactor-sso-details-endpoint";
139-
public const string LimitItemDeletion = "pm-15493-restrict-item-deletion-to-can-manage-permission";
140138
public const string PolicyRequirements = "pm-14439-policy-requirements";
141139
public const string ScimInviteUserOptimization = "pm-16811-optimize-invite-user-flow-to-fail-fast";
142140
public const string EventBasedOrganizationIntegrations = "event-based-organization-integrations";
143141
public const string SeparateCustomRolePermissions = "pm-19917-separate-custom-role-permissions";
144142
public const string CreateDefaultLocation = "pm-19467-create-default-location";
145143
public const string PM23845_VNextApplicationCache = "pm-24957-refactor-memory-application-cache";
146-
public const string CipherRepositoryBulkResourceCreation = "pm-24951-cipher-repository-bulk-resource-creation-service";
147144

148145
/* Auth Team */
149146
public const string TwoFactorExtensionDataPersistence = "pm-9115-two-factor-extension-data-persistence";
@@ -182,8 +179,9 @@ public static class FeatureFlagKeys
182179
public const string PM19422_AllowAutomaticTaxUpdates = "pm-19422-allow-automatic-tax-updates";
183180
public const string PM21821_ProviderPortalTakeover = "pm-21821-provider-portal-takeover";
184181
public const string PM22415_TaxIDWarnings = "pm-22415-tax-id-warnings";
185-
public const string PM23385_UseNewPremiumFlow = "pm-23385-use-new-premium-flow";
186182
public const string PM24996ImplementUpgradeFromFreeDialog = "pm-24996-implement-upgrade-from-free-dialog";
183+
public const string PM24032_NewNavigationPremiumUpgradeButton = "pm-24032-new-navigation-premium-upgrade-button";
184+
public const string PM23713_PremiumBadgeOpensNewPremiumUpgradeDialog = "pm-23713-premium-badge-opens-new-premium-upgrade-dialog";
187185

188186
/* Key Management Team */
189187
public const string ReturnErrorOnExistingKeypair = "return-error-on-existing-keypair";
@@ -233,7 +231,6 @@ public static class FeatureFlagKeys
233231
/* Vault Team */
234232
public const string PM8851_BrowserOnboardingNudge = "pm-8851-browser-onboarding-nudge";
235233
public const string PM9111ExtensionPersistAddEditForm = "pm-9111-extension-persist-add-edit-form";
236-
public const string SecurityTasks = "security-tasks";
237234
public const string CipherKeyEncryption = "cipher-key-encryption";
238235
public const string DesktopCipherForms = "pm-18520-desktop-cipher-forms";
239236
public const string PM19941MigrateCipherDomainToSdk = "pm-19941-migrate-cipher-domain-to-sdk";

src/Core/KeyManagement/UserKey/Implementations/RotateUserAccountkeysCommand.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ public class RotateUserAccountKeysCommand : IRotateUserAccountKeysCommand
2525
private readonly IdentityErrorDescriber _identityErrorDescriber;
2626
private readonly IWebAuthnCredentialRepository _credentialRepository;
2727
private readonly IPasswordHasher<User> _passwordHasher;
28-
private readonly IFeatureService _featureService;
2928

3029
/// <summary>
3130
/// Instantiates a new <see cref="RotateUserAccountKeysCommand"/>
@@ -61,7 +60,6 @@ public RotateUserAccountKeysCommand(IUserService userService, IUserRepository us
6160
_identityErrorDescriber = errors;
6261
_credentialRepository = credentialRepository;
6362
_passwordHasher = passwordHasher;
64-
_featureService = featureService;
6563
}
6664

6765
/// <inheritdoc />
@@ -103,15 +101,7 @@ public async Task<IdentityResult> RotateUserAccountKeysAsync(User user, RotateUs
103101
List<UpdateEncryptedDataForKeyRotation> saveEncryptedDataActions = new();
104102
if (model.Ciphers.Any())
105103
{
106-
var useBulkResourceCreationService = _featureService.IsEnabled(FeatureFlagKeys.CipherRepositoryBulkResourceCreation);
107-
if (useBulkResourceCreationService)
108-
{
109-
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation_vNext(user.Id, model.Ciphers));
110-
}
111-
else
112-
{
113-
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers));
114-
}
104+
saveEncryptedDataActions.Add(_cipherRepository.UpdateForKeyRotation(user.Id, model.Ciphers));
115105
}
116106

117107
if (model.Folders.Any())

src/Core/MailTemplates/Mjml/README.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
# MJML email templating
22

3-
This directory contains MJML templates for emails. MJML is a markup language designed to reduce the pain of coding responsive email templates. There are DRY features within the library which will improve code quality.
3+
This directory contains MJML templates for emails. MJML is a markup language designed to reduce the pain of coding responsive email templates. Component based development features in MJML improve code quality and reusability.
44

55
MJML stands for MailJet Markup Language.
66

77
## Implementation considerations
88

9-
These `MJML` templates are compiled into HTML which will then be further consumed by our HandleBars mail service. We can continue to use this service to assign values from our View Models. This leverages the existing infrastructure. It also means we can continue to use the double brace (`{{}}`) syntax within MJML since Handlebars can be used to assign values to those `{{variables}}`.
9+
These `MJML` templates are compiled into HTML which will then be further consumed by our Handlebars mail service. We can continue to use this service to assign values from our View Models. This leverages the existing infrastructure. It also means we can continue to use the double brace (`{{}}`) syntax within MJML since Handlebars can be used to assign values to those `{{variables}}`.
1010

1111
There is no change on how we interact with our view models.
1212

@@ -18,26 +18,23 @@ There is no change to how we create the `txt.hbs`. MJML does not impact how we c
1818

1919
## Building `MJML` files
2020

21-
```powershell
21+
```shell
2222
npm ci
2323

24-
# Build once, output is the ./out directory
24+
# Build *.html to ./out directory
2525
npm run build
2626

27-
# To build on changes to *.mjml and *.js files, new *.js/*.mjml files will not be tracked, you will need to run again
27+
# To build on changes to *.mjml and *.js files, new files will not be tracked, you will need to run again
2828
npm run build:watch
2929

30-
# clean ./out directory
31-
npm run build:clean
32-
33-
# Build *.html.hbs once, output is the ./out-hbs directory
30+
# Build *.html.hbs to ./out directory
3431
npm run build:hbs
3532

36-
# To build on changes to *.mjml and *.js files, new *.js/*.mjml files will not be tracked, you will need to run again
37-
npm run build:watch:hbs
33+
# Build minified *.html.hbs to ./out directory
34+
npm run build:minify
3835

39-
# clean ./out-hbs directory
40-
npm run build:clean:hbs
36+
# apply prettier formatting
37+
npm run prettier
4138
```
4239

4340
## Development
@@ -48,21 +45,24 @@ When using MJML templating you can use the above [commands](#building-mjml-files
4845

4946
Not all MJML tags have the same attributes, it is highly recommended to review the documentation on the official MJML website to understand the usages of each of the tags.
5047

51-
### Possible process
48+
### Recommended development
5249

53-
#### Initial email development might look something like:
50+
#### Mjml email template development
5451

55-
1. create `cool-email.mjml`
52+
1. create `cool-email.mjml` in appropriate team directory
5653
2. run `npm run build:watch`
5754
3. view compiled `HTML` output in a web browser
58-
4. iterate -> while `build:watch`'ing you should be able to refresh the browser page after the mjml re-compile to see the changes
55+
4. iterate -> while `build:watch`'ing you should be able to refresh the browser page after the mjml/js re-compile to see the changes
5956

6057
#### Testing with `IMailService`
6158

62-
After the email is developed from the [initial step](#initial-email-development-might-look-something-like) you'll probably want to make sure the email `{{variables}}` are populated properly by running it through an `IMailService`.
59+
After the email is developed from the [initial step](#mjml-email-template-development) make sure the email `{{variables}}` are populated properly by running it through an `IMailService` implementation.
6360

64-
1. run `npm run build:hbs`
61+
1. run `npm run build:minify`
6562
2. copy built `*.html.hbs` files from the build directory to a location the mail service can consume them
63+
3. run code that will send the email
64+
65+
The minified `html.hbs` artifacts are deliverables and must be placed into the correct `src/Core/MailTemplates/Handlebars/` directories in order to be used by `IMailService` implementations.
6666

6767
### Custom tags
6868

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
const mjml2html = require("mjml");
2+
const { registerComponent } = require("mjml-core");
3+
const fs = require("fs");
4+
const path = require("path");
5+
const glob = require("glob");
6+
7+
// Parse command line arguments
8+
const args = process.argv.slice(2); // Remove 'node' and script path
9+
10+
// Parse flags
11+
const flags = {
12+
minify: args.includes("--minify") || args.includes("-m"),
13+
watch: args.includes("--watch") || args.includes("-w"),
14+
hbs: args.includes("--hbs") || args.includes("-h"),
15+
trace: args.includes("--trace") || args.includes("-t"),
16+
clean: args.includes("--clean") || args.includes("-c"),
17+
help: args.includes("--help"),
18+
};
19+
20+
// Use __dirname to get absolute paths relative to the script location
21+
const config = {
22+
inputDir: path.join(__dirname, "emails"),
23+
outputDir: path.join(__dirname, "out"),
24+
minify: flags.minify,
25+
validationLevel: "strict",
26+
hbsOutput: flags.hbs,
27+
};
28+
29+
// Debug output
30+
if (flags.trace) {
31+
console.log("[DEBUG] Script location:", __dirname);
32+
console.log("[DEBUG] Input directory:", config.inputDir);
33+
console.log("[DEBUG] Output directory:", config.outputDir);
34+
}
35+
36+
// Ensure output directory exists
37+
if (!fs.existsSync(config.outputDir)) {
38+
fs.mkdirSync(config.outputDir, { recursive: true });
39+
if (flags.trace) {
40+
console.log("[INFO] Created output directory:", config.outputDir);
41+
}
42+
}
43+
44+
// Find all MJML files with absolute path
45+
const mjmlFiles = glob.sync(`${config.inputDir}/**/*.mjml`);
46+
47+
console.log(`\n[INFO] Found ${mjmlFiles.length} MJML file(s) to compile...`);
48+
49+
if (mjmlFiles.length === 0) {
50+
console.error("[ERROR] No MJML files found!");
51+
console.error("[ERROR] Looked in:", config.inputDir);
52+
console.error(
53+
"[ERROR] Does this directory exist?",
54+
fs.existsSync(config.inputDir),
55+
);
56+
process.exit(1);
57+
}
58+
59+
// Compile each MJML file
60+
let successCount = 0;
61+
let errorCount = 0;
62+
63+
mjmlFiles.forEach((filePath) => {
64+
try {
65+
const mjmlContent = fs.readFileSync(filePath, "utf8");
66+
const fileName = path.basename(filePath, ".mjml");
67+
const relativePath = path.relative(config.inputDir, filePath);
68+
69+
console.log(`\n[BUILD] Compiling: ${relativePath}`);
70+
71+
// Compile MJML to HTML
72+
const result = mjml2html(mjmlContent, {
73+
minify: config.minify,
74+
validationLevel: config.validationLevel,
75+
filePath: filePath, // Important: tells MJML where the file is for resolving includes
76+
mjmlConfigPath: __dirname, // Point to the directory with .mjmlconfig
77+
});
78+
79+
// Check for errors
80+
if (result.errors.length > 0) {
81+
console.error(`[ERROR] Failed to compile ${fileName}.mjml:`);
82+
result.errors.forEach((err) =>
83+
console.error(` ${err.formattedMessage}`),
84+
);
85+
errorCount++;
86+
return;
87+
}
88+
89+
// Calculate output path preserving directory structure
90+
const relativeDir = path.dirname(relativePath);
91+
const outputDir = path.join(config.outputDir, relativeDir);
92+
93+
// Ensure subdirectory exists
94+
if (!fs.existsSync(outputDir)) {
95+
fs.mkdirSync(outputDir, { recursive: true });
96+
}
97+
98+
const outputExtension = config.hbsOutput ? ".html.hbs" : ".html";
99+
const outputPath = path.join(outputDir, `${fileName}${outputExtension}`);
100+
fs.writeFileSync(outputPath, result.html);
101+
102+
console.log(
103+
`[OK] Built: ${fileName}.mjml → ${path.relative(__dirname, outputPath)}`,
104+
);
105+
successCount++;
106+
107+
// Log warnings if any
108+
if (result.warnings && result.warnings.length > 0) {
109+
console.warn(`[WARN] Warnings for ${fileName}.mjml:`);
110+
result.warnings.forEach((warn) =>
111+
console.warn(` ${warn.formattedMessage}`),
112+
);
113+
}
114+
} catch (error) {
115+
console.error(`[ERROR] Exception processing ${path.basename(filePath)}:`);
116+
console.error(` ${error.message}`);
117+
errorCount++;
118+
}
119+
});
120+
121+
console.log(`\n[SUMMARY] Compilation complete!`);
122+
console.log(` Success: ${successCount}`);
123+
console.log(` Failed: ${errorCount}`);
124+
console.log(` Output: ${config.outputDir}`);
125+
126+
if (errorCount > 0) {
127+
process.exit(1);
128+
}

0 commit comments

Comments
 (0)