Merged in chore/release-pipeline (pull request #3352)
Add scripts for handling deployments Approved-by: Linus Flood
This commit is contained in:
@@ -47,6 +47,7 @@ For more details see the respective apps and packages' README files.
|
||||
|
||||
## More documentation
|
||||
|
||||
- [Deploy](./docs/deploy.md)
|
||||
- [Icons](./docs/icons.md)
|
||||
- [Payment](./docs/payment.md)
|
||||
- [Translations (i18n)](./docs/translations.md)
|
||||
|
||||
2
apps/partner-sas/env/client.ts
vendored
2
apps/partner-sas/env/client.ts
vendored
@@ -5,11 +5,13 @@ export const env = createEnv({
|
||||
client: {
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().default("development"),
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001),
|
||||
NEXT_PUBLIC_RELEASE_TAG: z.string().optional(),
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
runtimeEnv: {
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE:
|
||||
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
|
||||
NEXT_PUBLIC_RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||
},
|
||||
})
|
||||
|
||||
2
apps/partner-sas/env/server.ts
vendored
2
apps/partner-sas/env/server.ts
vendored
@@ -36,6 +36,7 @@ export const env = createEnv({
|
||||
.refine((s) => s === "true" || s === "false")
|
||||
.transform((s) => s === "true")
|
||||
.default("false"),
|
||||
RELEASE_TAG: z.string().optional(),
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
runtimeEnv: {
|
||||
@@ -52,5 +53,6 @@ export const env = createEnv({
|
||||
SENTRY_ENVIRONMENT: process.env.NEXT_PUBLIC_SENTRY_ENVIRONMENT,
|
||||
SENTRY_SERVER_SAMPLERATE: process.env.SENTRY_SERVER_SAMPLERATE,
|
||||
REDEMPTION_ENABLED: process.env.REDEMPTION_ENABLED,
|
||||
RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ Sentry.init({
|
||||
denyUrls: denyUrls,
|
||||
// Disable logs for clients, will probably give us too much noise
|
||||
enableLogs: false,
|
||||
release: env.NEXT_PUBLIC_RELEASE_TAG || undefined,
|
||||
beforeSendLog(log) {
|
||||
const ignoredLevels: (typeof log.level)[] = ["debug", "trace", "info"]
|
||||
if (ignoredLevels.includes(log.level)) {
|
||||
|
||||
@@ -21,5 +21,6 @@ async function configureSentry() {
|
||||
tracesSampleRate: env.SENTRY_SERVER_SAMPLERATE,
|
||||
denyUrls: denyUrls,
|
||||
enableLogs: true,
|
||||
release: env.RELEASE_TAG || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[build]
|
||||
command = "yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
publish = "apps/partner-sas/.next"
|
||||
|
||||
ignore = "if [ -z ${CACHED_COMMIT_REF+x} ] ; then echo 'no CACHED_COMMIT_REF found' && false ; else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF apps/partner-sas packages/booking-flow packages/common packages/trpc packages/design-system packages/typescript-config ; fi"
|
||||
|
||||
[context.branch-deploy]
|
||||
command = "yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/partner-sas && yarn build:sas"
|
||||
|
||||
[build.environment]
|
||||
# set TERM variable for terminal output
|
||||
|
||||
@@ -27,11 +27,17 @@ export function EnvironmentWatermark() {
|
||||
}
|
||||
|
||||
const { environment, name } = getEnvironmentName()
|
||||
const displayValue = name === environment ? name : `${name} (${environment})`
|
||||
const displayValues = [name]
|
||||
if (name !== environment) {
|
||||
displayValues.push(`(${environment})`)
|
||||
}
|
||||
if (env.NEXT_PUBLIC_RELEASE_TAG) {
|
||||
displayValues.push(`[${env.NEXT_PUBLIC_RELEASE_TAG}]`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={variants({ environment: environment })}>
|
||||
<span>{displayValue}</span>
|
||||
<span>{displayValues.join(" ")}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
2
apps/scandic-web/env/client.ts
vendored
2
apps/scandic-web/env/client.ts
vendored
@@ -8,6 +8,7 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_SENTRY_ENVIRONMENT: z.string().default("development"),
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE: z.coerce.number().default(0.001),
|
||||
NEXT_PUBLIC_PUBLIC_URL: z.string().optional(),
|
||||
NEXT_PUBLIC_RELEASE_TAG: z.string().optional(),
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
runtimeEnv: {
|
||||
@@ -17,5 +18,6 @@ export const env = createEnv({
|
||||
NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE:
|
||||
process.env.NEXT_PUBLIC_SENTRY_CLIENT_SAMPLERATE,
|
||||
NEXT_PUBLIC_PUBLIC_URL: process.env.NEXT_PUBLIC_PUBLIC_URL,
|
||||
NEXT_PUBLIC_RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||
},
|
||||
})
|
||||
|
||||
2
apps/scandic-web/env/server.ts
vendored
2
apps/scandic-web/env/server.ts
vendored
@@ -112,6 +112,7 @@ export const env = createEnv({
|
||||
.refine((s) => s === "true" || s === "false")
|
||||
.transform((s) => s === "true")
|
||||
.default("false"),
|
||||
RELEASE_TAG: z.string().optional(),
|
||||
},
|
||||
emptyStringAsUndefined: true,
|
||||
runtimeEnv: {
|
||||
@@ -168,5 +169,6 @@ export const env = createEnv({
|
||||
NEW_STAYS_ON_MY_PAGES: process.env.NEW_STAYS_ON_MY_PAGES,
|
||||
SEO_INERT: process.env.SEO_INERT,
|
||||
ENABLE_PROFILE_CONSENT: process.env.ENABLE_PROFILE_CONSENT,
|
||||
RELEASE_TAG: process.env.NEXT_PUBLIC_RELEASE_TAG,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ Sentry.init({
|
||||
denyUrls: denyUrls,
|
||||
// Disable logs for clients, will probably give us too much noise
|
||||
enableLogs: false,
|
||||
release: env.NEXT_PUBLIC_RELEASE_TAG || undefined,
|
||||
beforeSendLog(log) {
|
||||
const ignoredLevels: (typeof log.level)[] = ["debug", "trace", "info"]
|
||||
if (ignoredLevels.includes(log.level)) {
|
||||
|
||||
@@ -22,5 +22,6 @@ async function configureSentry() {
|
||||
denyUrls: denyUrls,
|
||||
enableLogs: true,
|
||||
enableMetrics: true,
|
||||
release: env.RELEASE_TAG || undefined,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
[build]
|
||||
command = "yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
publish = "apps/scandic-web/.next"
|
||||
|
||||
ignore = "if [ -z ${CACHED_COMMIT_REF+x} ] ; then echo 'no CACHED_COMMIT_REF found' && false ; else git diff --quiet $CACHED_COMMIT_REF $COMMIT_REF apps/scandic-web packages/booking-flow packages/common packages/trpc packages/design-system packages/typescript-config ; fi"
|
||||
|
||||
[context.branch-deploy]
|
||||
command = "yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
|
||||
[context.deploy-preview]
|
||||
command = "yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
command = "export NEXT_PUBLIC_RELEASE_TAG=$(git tag -l 'release-v*' | sort -V | tail -n 1) && yarn test --filter=@scandic-hotels/scandic-web && yarn build:web"
|
||||
|
||||
[[plugins]]
|
||||
package = "@netlify/plugin-nextjs"
|
||||
|
||||
29
docs/deploy.md
Normal file
29
docs/deploy.md
Normal file
@@ -0,0 +1,29 @@
|
||||
## Deployment Script
|
||||
|
||||
This script automates the deployment process for the application by force-pushing the current branch to specific environment branches.
|
||||
Deploying to production must be done via a release-branch
|
||||
|
||||
### Usage
|
||||
|
||||
Be situated on the branch you want to deploy and then run the script using `yarn deploy:<environment>`
|
||||
|
||||
```bash
|
||||
yarn deploy:stage
|
||||
```
|
||||
|
||||
or if you want to explicitly call it by using `yarn dlx tsx scripts/deploy/deploy.ts` (or `bun`/`npx tsx`)
|
||||
|
||||
### Environments
|
||||
|
||||
| Environment | Target Branch | Description |
|
||||
| :---------- | :------------ | :----------------------------------------- |
|
||||
| `test` | `test` | Deploys to the test environment. |
|
||||
| `stage` | `stage` | Deploys to the staging environment. |
|
||||
| `preprod` | `prod` | Deploys to the pre-production environment. |
|
||||
| `prod` | `release` | Deploys to the production environment. |
|
||||
|
||||
### Features
|
||||
|
||||
- **Branch Validation**: Checks if the target environment is valid.
|
||||
- **Tagging**: If deploying from a release branch (e.g., `release-v1.2.3`), it automatically creates and pushes a git tag (e.g., `v1.2.3`) if it doesn't exist.
|
||||
- **Safety Check**: Prompts for confirmation before deploying.
|
||||
@@ -27,7 +27,10 @@
|
||||
"i18n:push": "yarn i18n:extract && yarn i18n:upload",
|
||||
"i18n:pull": "yarn i18n:download && yarn i18n:compile && yarn i18n:distribute",
|
||||
"i18n:sync": "yarn i18n:push && yarn i18n:pull",
|
||||
"i18n:syncDefaultMessage": "yarn i18n:download && bun scripts/i18n/syncDefaultMessage/index.ts scripts/i18n/translations/en.json '{apps,packages}/**/*.{tsx,ts}' && yarn format --force"
|
||||
"i18n:syncDefaultMessage": "yarn i18n:download && bun scripts/i18n/syncDefaultMessage/index.ts scripts/i18n/translations/en.json '{apps,packages}/**/*.{tsx,ts}' && yarn format --force",
|
||||
"deploy:stage": "yarn dlx tsx scripts/deploy/deploy.ts stage",
|
||||
"deploy:preprod": "yarn dlx tsx scripts/deploy/deploy.ts preprod",
|
||||
"deploy:prod": "yarn dlx tsx scripts/deploy/deploy.ts prod"
|
||||
},
|
||||
"workspaces": [
|
||||
"apps/*",
|
||||
|
||||
128
scripts/deploy/deploy.ts
Normal file
128
scripts/deploy/deploy.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
import { execSync } from "node:child_process";
|
||||
import { createInterface } from "node:readline";
|
||||
|
||||
// Configuration
|
||||
const ENV_BRANCH_MAP: Record<string, string> = {
|
||||
test: "test",
|
||||
stage: "stage",
|
||||
preprod: "prod",
|
||||
prod: "release",
|
||||
};
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const targetEnv = args[0];
|
||||
|
||||
// Validate input
|
||||
if (!targetEnv) {
|
||||
console.error("❌ Error: Please provide an environment.");
|
||||
console.log(
|
||||
`Usage: yarn dlx tsx deploy.ts <${Object.keys(ENV_BRANCH_MAP).join("|")}>`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetBranch = ENV_BRANCH_MAP[targetEnv];
|
||||
|
||||
if (!targetBranch) {
|
||||
console.error(`❌ Error: Invalid environment '${targetEnv}'.`);
|
||||
console.log(
|
||||
`Available environments: ${Object.keys(ENV_BRANCH_MAP).join(", ")}`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Git helper
|
||||
const runGit = (
|
||||
command: string,
|
||||
options: { stdio?: "inherit" | "ignore" | "pipe" } = {}
|
||||
) => {
|
||||
return execSync(command, { encoding: "utf-8", ...options }).trim();
|
||||
};
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Get current branch
|
||||
const currentBranch = runGit("git rev-parse --abbrev-ref HEAD");
|
||||
|
||||
console.log(
|
||||
`\n🚀 Preparing to deploy branch '${currentBranch}' to '${targetEnv}' (target branch: '${targetBranch}')`
|
||||
);
|
||||
|
||||
// Check for tagging requirement
|
||||
let tagToCreate: string | null = null;
|
||||
const releaseBranchRegex = /^release-(v\d+\.\d+\.\d+)$/;
|
||||
const releaseBranchMatch = currentBranch.match(releaseBranchRegex);
|
||||
|
||||
if (releaseBranchMatch) {
|
||||
const tagName = releaseBranchMatch[1];
|
||||
try {
|
||||
// Check if tag already exists locally or remote
|
||||
// Checking local first
|
||||
runGit(`git rev-parse ${tagName}`, { stdio: "ignore" });
|
||||
console.log(`ℹ️ Tag '${tagName}' already exists.`);
|
||||
} catch {
|
||||
// Tag doesn't exist
|
||||
tagToCreate = tagName;
|
||||
console.log(`✨ Will create and push tag: '${tagName}'`);
|
||||
}
|
||||
}
|
||||
if (!releaseBranchMatch && targetEnv === "prod") {
|
||||
console.warn(
|
||||
"⚠️ Warning: Deploying to prod from a non-release branch. No version tag will be created."
|
||||
);
|
||||
}
|
||||
|
||||
// Confirmation prompt
|
||||
const rl = createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const confirmed = await new Promise<boolean>((resolve) => {
|
||||
rl.question(
|
||||
`\n❓ Are you sure you want to deploy ${currentBranch} to ${targetEnv}? (y/n) `,
|
||||
(answer) => {
|
||||
rl.close();
|
||||
resolve(answer.toLowerCase() === "y");
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
if (!confirmed) {
|
||||
console.log("🚫 Deployment aborted.");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
console.log("\n🔄 Starting deployment...");
|
||||
|
||||
// 1. Create and push tag if needed
|
||||
if (tagToCreate) {
|
||||
console.log(`🏷️ Creating tag ${tagToCreate}...`);
|
||||
runGit(`git tag ${tagToCreate}`);
|
||||
|
||||
console.log(`⬆️ Pushing tag ${tagToCreate}...`);
|
||||
runGit(`git push origin ${tagToCreate}`);
|
||||
}
|
||||
|
||||
// 2. Force push to target branch
|
||||
console.log(
|
||||
`🔥 Force pushing '${currentBranch}' to 'origin/${targetBranch}'...`
|
||||
);
|
||||
runGit(
|
||||
`git push origin ${currentBranch}:${targetBranch} --force-with-lease`,
|
||||
{
|
||||
stdio: "inherit",
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`\n✅ Deployment to ${targetEnv} completed successfully!`);
|
||||
} catch (error) {
|
||||
console.error("\n❌ Deployment failed.");
|
||||
if (error instanceof Error) {
|
||||
console.error(error.message);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user