Skip to content

Commit 4de2404

Browse files
committed
chore: create-local-release-script
created a local release script for releasing repo packages to verdaccio
1 parent fb618ca commit 4de2404

9 files changed

Lines changed: 281 additions & 2 deletions

File tree

.changeset/config.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@forgerock/device-client",
1818
"@forgerock/davinci-app",
1919
"@forgerock/davinci-suites",
20-
"@forgerock/mock-api-v2"
20+
"@forgerock/mock-api-v2",
21+
"@forgerock/local-release-tool"
2122
]
2223
}

.github/workflows/publish.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
payload-delimiter: '_'
8484
webhook: ${{ secrets.SLACK_WEBHOOK_URL }}
8585
webhook-type: webhook-trigger
86-
payload: steps.changesets.outputs.publishedPackages
86+
payload: ${{ steps.changesets.outputs.publishedPackages }}
8787

8888
- uses: codecov/codecov-action@v5
8989
with:

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"docs": "nx affected --target=typedoc",
1919
"format": "pnpm nx format:write",
2020
"lint": "nx affected --target=lint",
21+
"local-release": "pnpm ts-node tools/release/release.ts",
2122
"nx": "nx",
2223
"preinstall": "npx only-allow pnpm",
2324
"prepare": "node .husky/install.mjs",
@@ -52,6 +53,7 @@
5253
"@commitlint/cli": "^19.1.0",
5354
"@commitlint/config-conventional": "^19.1.0",
5455
"@commitlint/prompt": "^19.1.0",
56+
"@effect/cli": "0.59.9",
5557
"@effect/language-service": "^0.2.0",
5658
"@effect/platform": "^0.58.27",
5759
"@effect/platform-node": "^0.53.26",
@@ -85,6 +87,7 @@
8587
"conventional-changelog-conventionalcommits": "^8.0.0",
8688
"cz-conventional-changelog": "^3.3.0",
8789
"cz-git": "^1.6.1",
90+
"effect": "^3.12.7",
8891
"eslint": "^9.8.0",
8992
"eslint-config-prettier": "10.1.2",
9093
"eslint-plugin-import": "2.31.0",

pnpm-lock.yaml

Lines changed: 69 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tools/release/commands/commands.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { Effect, Stream, Console } from 'effect';
2+
import { Command } from '@effect/platform';
3+
4+
export const buildPackages = Command.make('pnpm', 'build').pipe(
5+
Command.lines,
6+
Stream.tap((line) => Console.log(`Build: ${line}`)),
7+
Stream.runDrain,
8+
);
9+
10+
// Effect to check git status for staged files
11+
export const checkGitStatus = Command.make('git', 'status', '--porcelain').pipe(
12+
Command.string,
13+
Effect.flatMap((output) => {
14+
// Check if the output contains lines indicating staged changes (e.g., starting with M, A, D, R, C, U followed by a space)
15+
const stagedChanges = output.split('\n').some((line) => /^[MADRCU] /.test(line.trim()));
16+
if (stagedChanges) {
17+
return Effect.fail(
18+
'Git repository has staged changes. Please commit or stash them before releasing.',
19+
);
20+
}
21+
return Effect.void; // No staged changes
22+
}),
23+
// If the command fails (e.g., not a git repo), treat it as an error too.
24+
Effect.catchAll((error) => Effect.fail(`Git status check command failed: ${error}`)),
25+
Effect.tapError((error) => Console.error(error)), // Log the specific error message
26+
Effect.asVoid, // Don't need the output on success
27+
);
28+
29+
// Effect to run changesets snapshot
30+
export const runChangesetsSnapshot = Command.make(
31+
'pnpm',
32+
'changeset',
33+
'version',
34+
'--snapshot',
35+
'beta',
36+
).pipe(
37+
Command.lines,
38+
Stream.tap((line) => Console.log(`Changesets: ${line}`)),
39+
Stream.runDrain, // Consume the stream and wait for completion
40+
Effect.tapBoth({
41+
onFailure: (error) =>
42+
Effect.fail(Console.error(`Changesets snapshot command failed: ${error}`)),
43+
onSuccess: () => Console.log('Changesets snapshot completed successfully.'),
44+
}),
45+
Effect.asVoid,
46+
);
47+
48+
// Effect to start local registry (run in background)
49+
export const startLocalRegistry = Command.make('pnpm', 'nx', 'local-registry').pipe(
50+
Command.start, // Starts the process and returns immediately
51+
Effect.tap(() =>
52+
Console.log('Attempting to start local registry (Verdaccio) in the background...'),
53+
),
54+
Effect.tapError((error) => Console.error(`Failed to start local registry: ${error}`)),
55+
Effect.asVoid, // We don't need the Process handle for this script's logic
56+
);
57+
58+
export const restoreGitFiles = Command.make('git', 'restore', '.').pipe(Command.start);
59+
60+
export const publishPackages = Command.make(
61+
'pnpm',
62+
'publish',
63+
'-r',
64+
'--tag',
65+
'beta',
66+
'--registry=http://localhost:4873',
67+
'--no-git-checks',
68+
).pipe(
69+
Command.lines,
70+
Stream.tap((line) => Console.log(`Publish: ${line}`)),
71+
Stream.runDrain,
72+
Effect.tapBoth({
73+
onFailure: (error) => Effect.fail(() => Console.error(`Publishing failed: ${error}`)),
74+
onSuccess: () => Console.log('Packages were published successfully to the local registry.'),
75+
}),
76+
Effect.asVoid,
77+
);

tools/release/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "@forgerock/local-release-tool",
3+
"version": "0.0.0",
4+
"private": true,
5+
"description": "",
6+
"keywords": [],
7+
"license": "ISC",
8+
"author": "",
9+
"main": "index.js",
10+
"scripts": {},
11+
"dependencies": {}
12+
}

tools/release/release.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* eslint-disable import/extensions */
2+
import { Effect, Console } from 'effect';
3+
import { NodeContext, NodeRuntime } from '@effect/platform-node';
4+
import { FileSystem, Path } from '@effect/platform';
5+
import {
6+
checkGitStatus,
7+
startLocalRegistry,
8+
runChangesetsSnapshot,
9+
restoreGitFiles,
10+
publishPackages,
11+
buildPackages,
12+
} from './commands/commands';
13+
14+
const checkForChangesets = Effect.gen(function* () {
15+
yield* Console.log('Checking for changeset files...');
16+
17+
const fs = yield* FileSystem.FileSystem;
18+
const path = yield* Path.Path;
19+
20+
const changesetDir = path.join(process.cwd(), '.changeset');
21+
22+
const files = yield* fs.readDirectory(changesetDir).pipe(
23+
Effect.catchTag('SystemError', (e) => {
24+
if (e.reason === 'NotFound') {
25+
return Effect.fail('No changesets found. Please add a changeset before releasing.');
26+
}
27+
// Otherwise, propagate the error
28+
return Effect.fail(`An unexpected error occured ${e}`);
29+
}),
30+
);
31+
32+
const hasChangesetFiles = files
33+
.filter((file) => file !== 'README.md')
34+
.filter((file) => file !== 'config.json')
35+
.every((file) => file.endsWith('.md'));
36+
37+
if (!hasChangesetFiles) {
38+
yield* Effect.fail('No changesets found. Please add a changeset before releasing.');
39+
}
40+
41+
yield* Console.log('Changeset files found.');
42+
}).pipe(Effect.tapError((error) => Console.error(`Changeset check failed: ${error}`)));
43+
44+
const program = Effect.gen(function* () {
45+
yield* Console.log('Starting release script...');
46+
47+
yield* Console.log('Checking Git status for staged files...');
48+
yield* checkGitStatus;
49+
50+
yield* Console.log('Git status OK (no staged files found).');
51+
yield* checkForChangesets;
52+
53+
yield* Console.log('Building packages');
54+
yield* buildPackages;
55+
56+
yield* Console.log('Running Changesets snapshot version...');
57+
yield* runChangesetsSnapshot;
58+
59+
yield* Console.log('Starting Verdaccio');
60+
yield* startLocalRegistry;
61+
yield* Console.log('Waiting for local registry to initialize... (5 seconds)');
62+
yield* Effect.sleep('5 seconds');
63+
64+
yield* Console.log('Publishing packages to local registry...');
65+
yield* publishPackages;
66+
67+
yield* Console.log(
68+
'Release script finished. Local registry should still be running in the background.',
69+
);
70+
71+
yield* Console.log('Registry Url: -> http://localhost:4873');
72+
yield* restoreGitFiles;
73+
yield* Effect.never; // Keep script running if needed, e.g., for background process
74+
}).pipe(
75+
Effect.catchAll((error) => {
76+
if (typeof error === 'string') {
77+
return Console.error(`Error: ${error}`);
78+
}
79+
return Console.error(`An unexpected error occurred: ${JSON.stringify(error)}`);
80+
}),
81+
Effect.provide(NodeContext.layer),
82+
);
83+
84+
NodeRuntime.runMain(Effect.scoped(program));

tools/release/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"files": [],
4+
"include": ["**/*.ts"],
5+
"compilerOptions": {
6+
"rootDir": ".",
7+
"moduleResolution": "Node",
8+
"module": "CommonJS",
9+
"target": "ES2024",
10+
"strict": true
11+
},
12+
"nx": {
13+
"addTypecheckTarget": false
14+
}
15+
}

0 commit comments

Comments
 (0)