Skip to content

Commit ff43f93

Browse files
committed
chore: fix-local-release
disable changelog generation in local release, which requires a GH token.
1 parent e644572 commit ff43f93

2 files changed

Lines changed: 135 additions & 127 deletions

File tree

tools/release/commands/commands.ts

Lines changed: 107 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,123 @@
1-
import { Effect, Stream, Console } from 'effect';
1+
import { Effect, Console } from 'effect';
22
import { Command } from '@effect/platform';
3+
import { FileSystem, Path } from '@effect/platform';
34

4-
export const buildPackages = Command.make('pnpm', 'build').pipe(
5-
Command.string,
6-
Stream.tap((line) => Console.log(`Build: ${line}`)),
7-
Stream.runDrain,
8-
);
5+
const SNAPSHOT_TAG = 'beta';
6+
const LOCAL_REGISTRY_URL = 'http://localhost:4873';
7+
8+
/**
9+
* Runs a command with fully inherited stdio and `CI=true` so that tools
10+
* like Nx use non-interactive output. Fails if the exit code is non-zero.
11+
*/
12+
const runInherited = (cmd: Command.Command) =>
13+
cmd.pipe(
14+
Command.env({ CI: 'true' }),
15+
Command.stdin('inherit'),
16+
Command.stdout('inherit'),
17+
Command.stderr('inherit'),
18+
Command.exitCode,
19+
Effect.flatMap((code) =>
20+
code === 0 ? Effect.void : Effect.fail(`Command exited with code ${code}`),
21+
),
22+
);
923

10-
// Effect to check git status for staged files
11-
export const checkGitStatus = Command.make('git', 'status', '--porcelain').pipe(
24+
/** Fails if the git working tree has staged changes. */
25+
export const assertCleanGitStatus = Command.make('git', 'status', '--porcelain').pipe(
1226
Command.string,
1327
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
28+
const hasStagedChanges = output.split('\n').some((line) => /^[MADRCU] /.test(line.trim()));
29+
return hasStagedChanges
30+
? Effect.fail('Git has staged changes. Commit or stash them before releasing.')
31+
: Effect.void;
2232
}),
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
33+
Effect.tap(() => Console.log('Git status clean — no staged changes.')),
34+
Effect.tapError((error) => Console.error(`Git check failed: ${error}`)),
35+
Effect.asVoid,
2736
);
2837

29-
// Effect to run changesets snapshot
30-
export const runChangesetsSnapshot = Command.make(
31-
'pnpm',
32-
'changeset',
33-
'version',
34-
'--snapshot',
35-
'beta',
36-
).pipe(Command.exitCode);
38+
/** Fails if the `.changeset/` directory contains no changeset markdown files. */
39+
export const assertChangesetsExist = Effect.gen(function* () {
40+
const fs = yield* FileSystem.FileSystem;
41+
const path = yield* Path.Path;
42+
43+
const changesetDir = path.join(process.cwd(), '.changeset');
44+
45+
const files = yield* fs
46+
.readDirectory(changesetDir)
47+
.pipe(
48+
Effect.catchTag('SystemError', (e) =>
49+
Effect.fail(
50+
e.reason === 'NotFound'
51+
? 'No .changeset directory found.'
52+
: `Failed to read .changeset directory: ${e}`,
53+
),
54+
),
55+
);
56+
57+
const hasChangesets = files
58+
.filter((f) => f !== 'README.md' && f !== 'config.json')
59+
.some((f) => f.endsWith('.md'));
60+
61+
if (!hasChangesets) {
62+
yield* Effect.fail('No changeset files found. Add a changeset before releasing.');
63+
}
64+
65+
yield* Console.log('Changeset files found.');
66+
}).pipe(Effect.tapError((error) => Console.error(`Changeset check failed: ${error}`)));
67+
68+
/**
69+
* Versions all packages as snapshot releases via `changeset version --snapshot`.
70+
* Temporarily disables the GitHub changelog in `.changeset/config.json` to avoid
71+
* requiring a GITHUB_TOKEN for local releases. The modified config is reverted
72+
* by `restoreGitFiles`.
73+
*/
74+
export const versionSnapshotPackages = Effect.gen(function* () {
75+
const fs = yield* FileSystem.FileSystem;
76+
const path = yield* Path.Path;
77+
78+
const configPath = path.join(process.cwd(), '.changeset', 'config.json');
79+
const raw = yield* fs.readFileString(configPath);
80+
const config: Record<string, unknown> = JSON.parse(raw);
81+
config.changelog = false;
82+
yield* fs.writeFileString(configPath, JSON.stringify(config, null, 2) + '\n');
83+
84+
yield* Console.log('Running changeset version --snapshot...');
85+
yield* runInherited(Command.make('pnpm', 'changeset', 'version', '--snapshot', SNAPSHOT_TAG));
86+
yield* Console.log('Snapshot versioning complete.');
87+
});
3788

38-
// Effect to start local registry (run in background)
89+
/** Runs `pnpm build` with output visible in the terminal. */
90+
export const buildPackages = runInherited(Command.make('pnpm', 'build')).pipe(
91+
Effect.tap(() => Console.log('Build complete.')),
92+
);
93+
94+
/** Starts the Verdaccio local registry as a background process. */
3995
export const startLocalRegistry = Command.make('pnpm', 'nx', 'local-registry').pipe(
40-
Command.start, // Starts the process and returns immediately
41-
Effect.tap(() =>
42-
Console.log('Attempting to start local registry (Verdaccio) in the background...'),
43-
),
96+
Command.start,
97+
Effect.tap(() => Console.log('Verdaccio local registry starting...')),
4498
Effect.tapError((error) => Console.error(`Failed to start local registry: ${error}`)),
45-
Effect.asVoid, // We don't need the Process handle for this script's logic
99+
Effect.asVoid,
46100
);
47101

48-
export const restoreGitFiles = Command.make('git', 'restore', '.').pipe(Command.start);
49-
50-
export const publishPackages = Command.make(
51-
'pnpm',
52-
'publish',
53-
'-r',
54-
'--tag',
55-
'beta',
56-
'--registry=http://localhost:4873',
57-
'--no-git-checks',
102+
/** Publishes all packages to the local Verdaccio registry. */
103+
export const publishToLocalRegistry = runInherited(
104+
Command.make(
105+
'pnpm',
106+
'publish',
107+
'-r',
108+
'--tag',
109+
SNAPSHOT_TAG,
110+
`--registry=${LOCAL_REGISTRY_URL}`,
111+
'--no-git-checks',
112+
),
58113
).pipe(
59-
Command.string,
60-
Stream.tap((line) => Console.log(`Publish: ${line}`)),
61-
Stream.runDrain,
62-
Effect.tapBoth({
63-
onFailure: (error) => Effect.fail(() => Console.error(`Publishing failed: ${error}`)),
64-
onSuccess: () => Console.log('Packages were published successfully to the local registry.'),
65-
}),
114+
Effect.tap(() => Console.log('Packages published to local registry.')),
115+
Effect.tapError((error) => Console.error(`Publishing failed: ${error}`)),
116+
Effect.asVoid,
117+
);
118+
119+
/** Restores all modified files in the working tree via `git restore .`. */
120+
export const restoreGitFiles = Command.make('git', 'restore', '.').pipe(
121+
Command.exitCode,
66122
Effect.asVoid,
67123
);

tools/release/release.ts

Lines changed: 28 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,87 +1,39 @@
11
/* eslint-disable import/extensions */
22
import { Effect, Console } from 'effect';
33
import { NodeContext, NodeRuntime } from '@effect/platform-node';
4-
import { FileSystem, Path } from '@effect/platform';
54
import {
6-
checkGitStatus,
5+
assertCleanGitStatus,
6+
assertChangesetsExist,
7+
versionSnapshotPackages,
8+
buildPackages,
79
startLocalRegistry,
8-
runChangesetsSnapshot,
10+
publishToLocalRegistry,
911
restoreGitFiles,
10-
publishPackages,
11-
buildPackages,
1212
} from './commands/commands';
1313

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('Running Changesets snapshot version...');
54-
const exitCode = yield* runChangesetsSnapshot;
55-
56-
if (exitCode.valueOf() == 1) {
57-
return yield* Effect.fail('Failed to version all snapshots');
58-
}
59-
60-
yield* Console.log('Building packages');
61-
yield* buildPackages;
62-
63-
yield* Console.log('Starting Verdaccio');
64-
yield* startLocalRegistry;
65-
yield* Console.log('Waiting for local registry to initialize... (10 seconds)');
66-
yield* Effect.sleep('10 seconds');
67-
68-
yield* Console.log('Publishing packages to local registry...');
69-
yield* publishPackages;
70-
71-
yield* Console.log(
72-
'Release script finished. Local registry should still be running in the background.',
73-
);
74-
75-
yield* Console.log('Registry Url: -> http://localhost:4873');
76-
yield* restoreGitFiles;
77-
yield* Effect.never; // Keep script running if needed, e.g., for background process
78-
}).pipe(
79-
Effect.catchAll((error) => {
80-
if (typeof error === 'string') {
81-
return Console.error(`Error: ${error}`);
82-
}
83-
return Console.error(`An unexpected error occurred: ${JSON.stringify(error)}`);
84-
}),
14+
const REGISTRY_URL = 'http://localhost:4873';
15+
const REGISTRY_STARTUP_DELAY = '10 seconds';
16+
17+
const program = assertCleanGitStatus.pipe(
18+
Effect.andThen(assertChangesetsExist),
19+
Effect.andThen(versionSnapshotPackages),
20+
Effect.andThen(
21+
Effect.addFinalizer(() =>
22+
restoreGitFiles.pipe(
23+
Effect.tap(() => Console.log('Restored modified files via git restore.')),
24+
Effect.catchAll((error) => Console.error(`Failed to restore git files: ${error}`)),
25+
),
26+
),
27+
),
28+
Effect.andThen(buildPackages),
29+
Effect.andThen(startLocalRegistry),
30+
Effect.andThen(Effect.sleep(REGISTRY_STARTUP_DELAY)),
31+
Effect.andThen(publishToLocalRegistry),
32+
Effect.andThen(Console.log(`Local release complete. Registry running at ${REGISTRY_URL}`)),
33+
Effect.andThen(Effect.never),
34+
Effect.catchAll((error) =>
35+
Console.error(`Release failed: ${typeof error === 'string' ? error : JSON.stringify(error)}`),
36+
),
8537
Effect.provide(NodeContext.layer),
8638
);
8739

0 commit comments

Comments
 (0)