Skip to content

Commit 85bccfb

Browse files
committed
chore: create-local-release-script
created a local release script for releasing repo packages to verdaccio
1 parent 3676a79 commit 85bccfb

5 files changed

Lines changed: 233 additions & 1 deletion

File tree

.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: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"conventional-changelog-conventionalcommits": "^8.0.0",
8686
"cz-conventional-changelog": "^3.3.0",
8787
"cz-git": "^1.6.1",
88+
"effect": "^3.12.7",
8889
"eslint": "^9.8.0",
8990
"eslint-config-prettier": "10.1.2",
9091
"eslint-plugin-import": "2.31.0",
@@ -126,5 +127,8 @@
126127
},
127128
"nx": {
128129
"includedScripts": []
130+
},
131+
"dependencies": {
132+
"@effect/cli": "0.59.9"
129133
}
130134
}

pnpm-lock.yaml

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

tools/release/release.ts

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import { Effect, Console, Stream } from 'effect';
2+
import { Command } from '@effect/platform';
3+
import { NodeContext, NodeRuntime } from '@effect/platform-node';
4+
import { FileSystem, Path } from '@effect/platform';
5+
6+
// Effect to check git status for staged files
7+
const checkGitStatus = Command.make('git', 'status', '--porcelain').pipe(
8+
Command.string,
9+
Effect.flatMap((output) => {
10+
// Check if the output contains lines indicating staged changes (e.g., starting with M, A, D, R, C, U followed by a space)
11+
const stagedChanges = output.split('\n').some((line) => /^[MADRCU] /.test(line.trim()));
12+
if (stagedChanges) {
13+
return Effect.fail(
14+
'Git repository has staged changes. Please commit or stash them before releasing.',
15+
);
16+
}
17+
return Effect.void; // No staged changes
18+
}),
19+
// If the command fails (e.g., not a git repo), treat it as an error too.
20+
Effect.catchAll((error) => Effect.fail(`Git status check command failed: ${error}`)),
21+
Effect.tapError((error) => Console.error(error)), // Log the specific error message
22+
Effect.asVoid, // Don't need the output on success
23+
);
24+
25+
// Effect to run changesets snapshot
26+
const runChangesetsSnapshot = Command.make(
27+
'pnpm',
28+
'changesets',
29+
'version',
30+
'--snapshot',
31+
'beta',
32+
).pipe(
33+
Command.lines, // Get output line by line
34+
Effect.tap((line) => Console.log(`Changesets: ${line}`)), // Log output
35+
Stream.runDrain, // Consume the stream and wait for completion
36+
Effect.tapBoth({
37+
onFailure: (error) => Console.error(`Changesets snapshot command failed: ${error}`),
38+
onSuccess: () => Console.log('Changesets snapshot completed successfully.'),
39+
}),
40+
Effect.asVoid, // Don't need the final result
41+
);
42+
43+
// Effect to start local registry (run in background)
44+
const startLocalRegistry = Command.make('pnpm', 'nx', 'local-registry').pipe(
45+
Command.start, // Starts the process and returns immediately
46+
Effect.tap(() =>
47+
Console.log('Attempting to start local registry (Verdaccio) in the background...'),
48+
),
49+
Effect.tapError((error) => Console.error(`Failed to start local registry: ${error}`)),
50+
Effect.asVoid, // We don't need the Process handle for this script's logic
51+
);
52+
53+
const restoreGitFiles = Command.make('git', 'restore', '.').pipe(Command.start);
54+
const publishPackages = Command.make(
55+
'pnpm',
56+
'publish',
57+
'-r',
58+
'--tag',
59+
'beta',
60+
'--registry=http://localhost:4873',
61+
).pipe(
62+
Command.lines, // Stream output line by line
63+
Effect.tap((line) => Console.log(`Publish: ${line}`)),
64+
Stream.runDrain,
65+
Effect.tapBoth({
66+
onFailure: (error) => Effect.fail(() => Console.error(`Publishing failed: ${error}`)),
67+
onSuccess: () => Console.log('Packages were published successfully to the local registry.'),
68+
}),
69+
Effect.asVoid,
70+
);
71+
72+
// Effect to check for the presence of changeset files
73+
const checkForChangesets = Effect.gen(function* () {
74+
yield* Console.log('Checking for changeset files...');
75+
76+
const fs = yield* FileSystem.FileSystem;
77+
const path = yield* Path.Path;
78+
79+
const changesetDir = path.join(process.cwd(), '.changesets');
80+
81+
// 3. Read the directory and check for *.md files (excluding README.md)
82+
const files = yield* fs.readDirectory(changesetDir).pipe(
83+
Effect.catchTag('SystemError', (e) => {
84+
// If the directory doesn't exist, treat it as "no changesets found"
85+
if (e.reason === 'NotFound') {
86+
return Effect.fail('No changesets found. Please add a changeset before releasing.');
87+
}
88+
// Otherwise, propagate the error
89+
return Effect.fail(`An unexpected error occured ${e}`);
90+
}),
91+
);
92+
93+
const hasChangesetFiles = files.every((file) => file.endsWith('.md') && file !== 'README.md');
94+
95+
if (!hasChangesetFiles) {
96+
yield* Effect.fail('No changesets found. Please add a changeset before releasing.');
97+
}
98+
99+
yield* Console.log('Changeset files found.');
100+
}).pipe(
101+
Effect.tapError((error) => Console.error(`Changeset check failed: ${error}`)), // Log specific failure reason
102+
);
103+
104+
// Main program flow using Effect.gen
105+
const program = Effect.gen(function* () {
106+
yield* Console.log('Starting release script...');
107+
108+
yield* Console.log('Checking Git status for staged files...');
109+
yield* checkGitStatus; // This will fail the Effect if staged changes exist
110+
yield* Console.log('Git status OK (no staged files found).');
111+
112+
// Check for changesets *before* running snapshot
113+
yield* checkForChangesets;
114+
115+
yield* Console.log('Running Changesets snapshot version...');
116+
yield* runChangesetsSnapshot;
117+
118+
yield* startLocalRegistry;
119+
// Add delay to give Verdaccio time to start up. Adjust duration if needed.
120+
yield* Console.log('Waiting for local registry to initialize... (5 seconds)');
121+
yield* Effect.sleep('5 seconds');
122+
123+
yield* Console.log('Publishing packages to local registry...');
124+
yield* publishPackages;
125+
126+
yield* Console.log(
127+
'Release script finished. Local registry should still be running in the background.',
128+
);
129+
130+
yield* restoreGitFiles;
131+
132+
yield* Effect.never; // Keep script running if needed, e.g., for background process
133+
}).pipe(
134+
// Fallback for any other Failures (like strings from Effect.fail)
135+
Effect.catchAll((error) => {
136+
if (typeof error === 'string') {
137+
return Console.error(`Error: ${error}`);
138+
}
139+
// Handle unexpected error types if necessary
140+
return Console.error(`An unexpected error occurred: ${JSON.stringify(error)}`);
141+
}),
142+
Effect.provide(NodeContext.layer), // Provide necessary Node services (includes FileSystem, Path, CommandExecutor)
143+
);
144+
145+
// Execute the program using the Node runtime
146+
NodeRuntime.runMain(Effect.scoped(program));

tools/release/tsconfig.json

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

0 commit comments

Comments
 (0)