alepha 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/api/audits/index.d.ts +80 -1
- package/dist/api/audits/index.d.ts.map +1 -1
- package/dist/api/audits/index.js.map +1 -1
- package/dist/api/files/index.d.ts +80 -1
- package/dist/api/files/index.d.ts.map +1 -1
- package/dist/api/files/index.js.map +1 -1
- package/dist/api/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.map +1 -1
- package/dist/api/jobs/index.js.map +1 -1
- package/dist/api/notifications/index.d.ts +21 -1
- package/dist/api/notifications/index.d.ts.map +1 -1
- package/dist/api/parameters/index.d.ts +451 -4
- package/dist/api/parameters/index.d.ts.map +1 -1
- package/dist/api/parameters/index.js.map +1 -1
- package/dist/api/users/index.d.ts +252 -249
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/api/users/index.js +4 -0
- package/dist/api/users/index.js.map +1 -1
- package/dist/api/verifications/index.d.ts +128 -128
- package/dist/api/verifications/index.d.ts.map +1 -1
- package/dist/batch/index.js.map +1 -1
- package/dist/cache/core/index.js.map +1 -1
- package/dist/cli/index.d.ts +304 -115
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +650 -531
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +210 -13
- package/dist/command/index.d.ts.map +1 -1
- package/dist/command/index.js +306 -69
- package/dist/command/index.js.map +1 -1
- package/dist/core/index.browser.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +7 -6
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.native.js +7 -6
- package/dist/core/index.native.js.map +1 -1
- package/dist/datetime/index.js.map +1 -1
- package/dist/fake/index.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/lock/redis/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/orm/index.browser.js +26 -5
- package/dist/orm/index.browser.js.map +1 -1
- package/dist/orm/index.d.ts +294 -215
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +522 -523
- package/dist/orm/index.js.map +1 -1
- package/dist/queue/redis/index.js +2 -4
- package/dist/queue/redis/index.js.map +1 -1
- package/dist/redis/index.d.ts +400 -29
- package/dist/redis/index.d.ts.map +1 -1
- package/dist/redis/index.js +412 -21
- package/dist/redis/index.js.map +1 -1
- package/dist/retry/index.js.map +1 -1
- package/dist/router/index.js.map +1 -1
- package/dist/scheduler/index.js.map +1 -1
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/cache/index.js.map +1 -1
- package/dist/server/cookies/index.browser.js.map +1 -1
- package/dist/server/cookies/index.js.map +1 -1
- package/dist/server/core/index.browser.js.map +1 -1
- package/dist/server/core/index.d.ts +0 -1
- package/dist/server/core/index.d.ts.map +1 -1
- package/dist/server/core/index.js.map +1 -1
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.map +1 -1
- package/dist/server/helmet/index.js.map +1 -1
- package/dist/server/links/index.browser.js.map +1 -1
- package/dist/server/links/index.js.map +1 -1
- package/dist/server/multipart/index.d.ts.map +1 -1
- package/dist/server/multipart/index.js.map +1 -1
- package/dist/server/proxy/index.js.map +1 -1
- package/dist/server/rate-limit/index.js.map +1 -1
- package/dist/server/security/index.d.ts +9 -9
- package/dist/server/security/index.js.map +1 -1
- package/dist/server/swagger/index.js.map +1 -1
- package/dist/thread/index.js.map +1 -1
- package/dist/topic/core/index.js.map +1 -1
- package/dist/topic/redis/index.js +3 -3
- package/dist/topic/redis/index.js.map +1 -1
- package/dist/vite/index.js +9 -6
- package/dist/vite/index.js.map +1 -1
- package/dist/websocket/index.browser.js.map +1 -1
- package/dist/websocket/index.d.ts +7 -7
- package/dist/websocket/index.js.map +1 -1
- package/package.json +3 -3
- package/src/api/users/index.ts +4 -0
- package/src/cli/apps/AlephaCli.ts +36 -14
- package/src/cli/apps/AlephaPackageBuilderCli.ts +5 -1
- package/src/cli/assets/appRouterTs.ts +1 -1
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/{ViteCommands.ts → build.ts} +4 -93
- package/src/cli/commands/changelog.ts +244 -0
- package/src/cli/commands/clean.ts +14 -0
- package/src/cli/commands/{DrizzleCommands.ts → db.ts} +37 -124
- package/src/cli/commands/deploy.ts +118 -0
- package/src/cli/commands/dev.ts +57 -0
- package/src/cli/commands/format.ts +17 -0
- package/src/cli/commands/{CoreCommands.ts → init.ts} +2 -40
- package/src/cli/commands/lint.ts +17 -0
- package/src/cli/commands/root.ts +32 -0
- package/src/cli/commands/run.ts +24 -0
- package/src/cli/commands/test.ts +42 -0
- package/src/cli/commands/typecheck.ts +19 -0
- package/src/cli/commands/{VerifyCommands.ts → verify.ts} +1 -13
- package/src/cli/defineConfig.ts +24 -0
- package/src/cli/index.ts +17 -5
- package/src/cli/services/AlephaCliUtils.ts +4 -21
- package/src/cli/services/GitMessageParser.ts +77 -0
- package/src/command/helpers/EnvUtils.ts +37 -0
- package/src/command/index.ts +3 -1
- package/src/command/primitives/$command.ts +172 -6
- package/src/command/providers/CliProvider.ts +424 -91
- package/src/core/Alepha.ts +8 -5
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.browser.ts +1 -1
- package/src/orm/index.ts +18 -10
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- package/src/orm/providers/{PostgresTypeProvider.ts → DatabaseTypeProvider.ts} +25 -3
- package/src/orm/providers/drivers/BunPostgresProvider.ts +225 -0
- package/src/orm/providers/drivers/BunSqliteProvider.ts +180 -0
- package/src/orm/providers/drivers/DatabaseProvider.ts +25 -0
- package/src/orm/providers/drivers/NodePostgresProvider.ts +0 -25
- package/src/orm/services/QueryManager.ts +10 -125
- package/src/queue/redis/providers/RedisQueueProvider.ts +2 -7
- package/src/redis/index.ts +65 -3
- package/src/redis/providers/BunRedisProvider.ts +304 -0
- package/src/redis/providers/BunRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/NodeRedisProvider.ts +280 -0
- package/src/redis/providers/NodeRedisSubscriberProvider.ts +94 -0
- package/src/redis/providers/RedisProvider.ts +134 -140
- package/src/redis/providers/RedisSubscriberProvider.ts +58 -49
- package/src/server/core/providers/BunHttpServerProvider.ts +0 -3
- package/src/server/core/providers/ServerBodyParserProvider.ts +3 -1
- package/src/server/core/providers/ServerProvider.ts +7 -4
- package/src/server/multipart/providers/ServerMultipartProvider.ts +3 -1
- package/src/server/proxy/providers/ServerProxyProvider.ts +1 -1
- package/src/topic/redis/providers/RedisTopicProvider.ts +3 -3
- package/src/vite/tasks/buildServer.ts +1 -0
- package/src/cli/commands/BiomeCommands.ts +0 -29
- package/src/cli/commands/ChangelogCommands.ts +0 -389
- package/src/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -1,56 +1,65 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
public async connect(): Promise<void> {
|
|
31
|
-
this.log.debug("Connecting...");
|
|
32
|
-
await this.client.connect();
|
|
33
|
-
this.log.info("Connection OK");
|
|
34
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Abstract Redis subscriber provider interface.
|
|
3
|
+
*
|
|
4
|
+
* This abstract class defines the common interface for Redis pub/sub subscriptions.
|
|
5
|
+
* Implementations include:
|
|
6
|
+
* - {@link NodeRedisSubscriberProvider} - Uses `@redis/client` for Node.js runtime
|
|
7
|
+
* - {@link BunRedisSubscriberProvider} - Uses Bun's native `RedisClient` for Bun runtime
|
|
8
|
+
*
|
|
9
|
+
* Redis requires separate connections for pub/sub operations, so this provider
|
|
10
|
+
* creates a dedicated connection for subscriptions.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* // Inject the abstract provider - runtime selects the implementation
|
|
15
|
+
* const subscriber = alepha.inject(RedisSubscriberProvider);
|
|
16
|
+
*
|
|
17
|
+
* // Subscribe to a channel
|
|
18
|
+
* await subscriber.subscribe("my-channel", (message, channel) => {
|
|
19
|
+
* console.log(`Received: ${message} on ${channel}`);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export abstract class RedisSubscriberProvider {
|
|
24
|
+
/**
|
|
25
|
+
* Whether the Redis subscriber client is ready to accept commands.
|
|
26
|
+
*/
|
|
27
|
+
public abstract readonly isReady: boolean;
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
29
|
+
/**
|
|
30
|
+
* Connect to the Redis server for subscriptions.
|
|
31
|
+
*/
|
|
32
|
+
public abstract connect(): Promise<void>;
|
|
41
33
|
|
|
42
34
|
/**
|
|
43
|
-
*
|
|
35
|
+
* Close the subscriber connection.
|
|
44
36
|
*/
|
|
45
|
-
|
|
46
|
-
const client = this.redisProvider.duplicate();
|
|
37
|
+
public abstract close(): Promise<void>;
|
|
47
38
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Subscribe to a channel.
|
|
41
|
+
*
|
|
42
|
+
* @param channel The channel name.
|
|
43
|
+
* @param callback The callback to invoke when a message is received.
|
|
44
|
+
*/
|
|
45
|
+
public abstract subscribe(
|
|
46
|
+
channel: string,
|
|
47
|
+
callback: SubscribeCallback,
|
|
48
|
+
): Promise<void>;
|
|
53
49
|
|
|
54
|
-
|
|
55
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Unsubscribe from a channel.
|
|
52
|
+
*
|
|
53
|
+
* @param channel The channel name.
|
|
54
|
+
* @param callback Optional specific callback to remove.
|
|
55
|
+
*/
|
|
56
|
+
public abstract unsubscribe(
|
|
57
|
+
channel: string,
|
|
58
|
+
callback?: SubscribeCallback,
|
|
59
|
+
): Promise<void>;
|
|
56
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Callback for subscription messages.
|
|
64
|
+
*/
|
|
65
|
+
export type SubscribeCallback = (message: string, channel: string) => void;
|
|
@@ -21,9 +21,6 @@ declare module "alepha" {
|
|
|
21
21
|
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
// Type declaration for Bun global (only when running in Bun runtime)
|
|
25
|
-
declare const Bun: any;
|
|
26
|
-
|
|
27
24
|
export class BunHttpServerProvider extends ServerProvider {
|
|
28
25
|
protected readonly alepha = $inject(Alepha);
|
|
29
26
|
protected readonly dateTimeProvider = $inject(DateTimeProvider);
|
|
@@ -36,7 +36,9 @@ export class ServerBodyParserProvider {
|
|
|
36
36
|
} else if (request.raw.node?.req) {
|
|
37
37
|
// convert Node.js IncomingMessage to Web ReadableStream
|
|
38
38
|
// TODO: check performance impact, it's better to directly read from Node stream!
|
|
39
|
-
stream = WebStream.from(
|
|
39
|
+
stream = WebStream.from(
|
|
40
|
+
request.raw.node.req,
|
|
41
|
+
) as unknown as ReadableStream;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
if (!stream) {
|
|
@@ -212,10 +212,13 @@ export class ServerProvider {
|
|
|
212
212
|
|
|
213
213
|
// if response.body is node stream
|
|
214
214
|
if (response.body instanceof Readable) {
|
|
215
|
-
ev.res = new Response(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
ev.res = new Response(
|
|
216
|
+
Readable.toWeb(response.body) as unknown as ReadableStream,
|
|
217
|
+
{
|
|
218
|
+
status: response.status,
|
|
219
|
+
headers: response.headers,
|
|
220
|
+
},
|
|
221
|
+
);
|
|
219
222
|
return;
|
|
220
223
|
}
|
|
221
224
|
|
|
@@ -59,7 +59,9 @@ export class ServerMultipartProvider {
|
|
|
59
59
|
webRequest = new Request(request.url, {
|
|
60
60
|
method: request.method,
|
|
61
61
|
headers: request.headers,
|
|
62
|
-
body: WebStream.from(
|
|
62
|
+
body: WebStream.from(
|
|
63
|
+
request.raw.node.req,
|
|
64
|
+
) as unknown as ReadableStream,
|
|
63
65
|
duplex: "half",
|
|
64
66
|
} as RequestInit & { duplex: "half" });
|
|
65
67
|
}
|
|
@@ -42,7 +42,7 @@ export class RedisTopicProvider extends TopicProvider {
|
|
|
42
42
|
* Publish a message to a topic.
|
|
43
43
|
*/
|
|
44
44
|
public async publish(topic: string, message: string): Promise<void> {
|
|
45
|
-
await this.redisProvider.
|
|
45
|
+
await this.redisProvider.publish(this.prefix(topic), message);
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
/**
|
|
@@ -53,7 +53,7 @@ export class RedisTopicProvider extends TopicProvider {
|
|
|
53
53
|
callback: SubscribeCallback,
|
|
54
54
|
): Promise<UnSubscribeFn> {
|
|
55
55
|
const topic = this.prefix(name);
|
|
56
|
-
await this.redisSubscriberProvider.
|
|
56
|
+
await this.redisSubscriberProvider.subscribe(topic, callback);
|
|
57
57
|
|
|
58
58
|
return () => this.unsubscribe(name, callback);
|
|
59
59
|
}
|
|
@@ -67,6 +67,6 @@ export class RedisTopicProvider extends TopicProvider {
|
|
|
67
67
|
): Promise<void> {
|
|
68
68
|
const topic = this.prefix(name);
|
|
69
69
|
|
|
70
|
-
await this.redisSubscriberProvider.
|
|
70
|
+
await this.redisSubscriberProvider.unsubscribe(topic, callback);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { $inject } from "alepha";
|
|
2
|
-
import { $command } from "alepha/command";
|
|
3
|
-
import { $logger } from "alepha/logger";
|
|
4
|
-
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
5
|
-
|
|
6
|
-
export class BiomeCommands {
|
|
7
|
-
protected readonly log = $logger();
|
|
8
|
-
protected readonly utils = $inject(AlephaCliUtils);
|
|
9
|
-
|
|
10
|
-
public readonly format = $command({
|
|
11
|
-
name: "format",
|
|
12
|
-
description: "Format the codebase using Biome",
|
|
13
|
-
handler: async ({ root }) => {
|
|
14
|
-
await this.utils.ensureConfig(root, { biomeJson: true });
|
|
15
|
-
await this.utils.ensureDependency(root, "@biomejs/biome");
|
|
16
|
-
await this.utils.exec(`biome format --fix`);
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
public readonly lint = $command({
|
|
21
|
-
name: "lint",
|
|
22
|
-
description: "Run linter across the codebase using Biome",
|
|
23
|
-
handler: async ({ root }) => {
|
|
24
|
-
await this.utils.ensureConfig(root, { biomeJson: true });
|
|
25
|
-
await this.utils.ensureDependency(root, "@biomejs/biome");
|
|
26
|
-
await this.utils.exec(`biome check --formatter-enabled=false --fix`);
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
}
|
|
@@ -1,389 +0,0 @@
|
|
|
1
|
-
import { exec } from "node:child_process";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
import { promisify } from "node:util";
|
|
5
|
-
import { t } from "alepha";
|
|
6
|
-
import { $command } from "alepha/command";
|
|
7
|
-
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
|
-
|
|
10
|
-
interface Commit {
|
|
11
|
-
hash: string;
|
|
12
|
-
type: string;
|
|
13
|
-
scope: string | null;
|
|
14
|
-
description: string;
|
|
15
|
-
breaking: boolean;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface ChangelogEntry {
|
|
19
|
-
version: string;
|
|
20
|
-
date: string;
|
|
21
|
-
features: Commit[];
|
|
22
|
-
fixes: Commit[];
|
|
23
|
-
docs: Commit[];
|
|
24
|
-
improvements: Commit[];
|
|
25
|
-
breaking: Commit[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
interface ChangelogConfig {
|
|
29
|
-
/**
|
|
30
|
-
* Commit prefixes to ignore (e.g., "project", "release", "chore")
|
|
31
|
-
*/
|
|
32
|
-
ignore?: string[];
|
|
33
|
-
/**
|
|
34
|
-
* Module scopes to recognize (e.g., "ui", "cli", "core")
|
|
35
|
-
* If not provided, all non-ignored scopes are included
|
|
36
|
-
*/
|
|
37
|
-
scopes?: string[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const DEFAULT_IGNORE = [
|
|
41
|
-
"project",
|
|
42
|
-
"release",
|
|
43
|
-
"starter",
|
|
44
|
-
"example",
|
|
45
|
-
"chore",
|
|
46
|
-
"ci",
|
|
47
|
-
"build",
|
|
48
|
-
"test",
|
|
49
|
-
"style",
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
function parseCommit(line: string, config: ChangelogConfig): Commit | null {
|
|
53
|
-
const match = line.match(/^([a-f0-9]+)\s+(.+)$/);
|
|
54
|
-
if (!match) return null;
|
|
55
|
-
|
|
56
|
-
const [, hash, message] = match;
|
|
57
|
-
const breaking =
|
|
58
|
-
message.includes("!:") || message.toLowerCase().includes("breaking");
|
|
59
|
-
const ignore = config.ignore ?? DEFAULT_IGNORE;
|
|
60
|
-
|
|
61
|
-
// Conventional commit: feat(scope): description or feat!: description
|
|
62
|
-
const conventionalMatch = message.match(
|
|
63
|
-
/^(feat|fix|docs|refactor|perf|revert)(?:\(([^)]+)\))?!?:\s*(.+)$/i,
|
|
64
|
-
);
|
|
65
|
-
if (conventionalMatch) {
|
|
66
|
-
const [, type, scope, description] = conventionalMatch;
|
|
67
|
-
|
|
68
|
-
// Skip if scope matches ignore list
|
|
69
|
-
if (scope) {
|
|
70
|
-
const baseScope = scope.split("/")[0];
|
|
71
|
-
if (ignore.includes(baseScope) || ignore.includes(scope)) {
|
|
72
|
-
return null;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Skip if type (without scope) matches ignore list (e.g., "docs" in ignore)
|
|
77
|
-
if (!scope && ignore.includes(type.toLowerCase())) {
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
hash: hash.substring(0, 8),
|
|
83
|
-
type: type.toLowerCase(),
|
|
84
|
-
scope: scope || null,
|
|
85
|
-
description: description.trim(),
|
|
86
|
-
breaking,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Module-specific commit: module: description or module/submodule: description
|
|
91
|
-
const moduleMatch = message.match(
|
|
92
|
-
/^([a-z][a-z0-9-]*(?:\/[a-z][a-z0-9-]*)?):\s*(.+)$/i,
|
|
93
|
-
);
|
|
94
|
-
if (moduleMatch) {
|
|
95
|
-
const [, module, description] = moduleMatch;
|
|
96
|
-
const baseModule = module.split("/")[0];
|
|
97
|
-
|
|
98
|
-
// Skip ignored prefixes
|
|
99
|
-
if (ignore.includes(baseModule)) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// If scopes are defined, check if this is a known scope
|
|
104
|
-
if (config.scopes && !config.scopes.includes(baseModule)) {
|
|
105
|
-
return null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Determine type based on description keywords
|
|
109
|
-
const desc = description.toLowerCase();
|
|
110
|
-
let type = "improve";
|
|
111
|
-
if (
|
|
112
|
-
desc.includes("fix") ||
|
|
113
|
-
desc.includes("bug") ||
|
|
114
|
-
desc.includes("issue")
|
|
115
|
-
) {
|
|
116
|
-
type = "fix";
|
|
117
|
-
} else if (
|
|
118
|
-
desc.includes("add") ||
|
|
119
|
-
desc.includes("new") ||
|
|
120
|
-
desc.includes("implement")
|
|
121
|
-
) {
|
|
122
|
-
type = "feat";
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return {
|
|
126
|
-
hash: hash.substring(0, 8),
|
|
127
|
-
type,
|
|
128
|
-
scope: module,
|
|
129
|
-
description: description.trim(),
|
|
130
|
-
breaking,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function formatCommit(commit: Commit): string {
|
|
138
|
-
const scope = commit.scope ? `**${commit.scope}**: ` : "";
|
|
139
|
-
return `- ${scope}${commit.description} (\`${commit.hash}\`)`;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function generateChangelog(entries: ChangelogEntry[]): string {
|
|
143
|
-
let output = "# Changelog\n\n";
|
|
144
|
-
output +=
|
|
145
|
-
"All notable changes to this project will be documented in this file.\n\n";
|
|
146
|
-
|
|
147
|
-
for (const entry of entries) {
|
|
148
|
-
output += `## [${entry.version}] - ${entry.date}\n\n`;
|
|
149
|
-
output += formatEntry(entry);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return output;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function formatEntry(entry: ChangelogEntry): string {
|
|
156
|
-
let output = "";
|
|
157
|
-
|
|
158
|
-
if (entry.breaking.length > 0) {
|
|
159
|
-
output += "### Breaking Changes\n\n";
|
|
160
|
-
for (const commit of entry.breaking) {
|
|
161
|
-
output += `${formatCommit(commit)}\n`;
|
|
162
|
-
}
|
|
163
|
-
output += "\n";
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (entry.features.length > 0) {
|
|
167
|
-
output += "### Features\n\n";
|
|
168
|
-
for (const commit of entry.features) {
|
|
169
|
-
output += `${formatCommit(commit)}\n`;
|
|
170
|
-
}
|
|
171
|
-
output += "\n";
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
if (entry.fixes.length > 0) {
|
|
175
|
-
output += "### Bug Fixes\n\n";
|
|
176
|
-
for (const commit of entry.fixes) {
|
|
177
|
-
output += `${formatCommit(commit)}\n`;
|
|
178
|
-
}
|
|
179
|
-
output += "\n";
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return output;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
async function loadConfig(root: string): Promise<ChangelogConfig> {
|
|
186
|
-
try {
|
|
187
|
-
const pkgPath = join(root, "package.json");
|
|
188
|
-
const pkg = JSON.parse(await readFile(pkgPath, "utf8"));
|
|
189
|
-
return pkg.changelog ?? {};
|
|
190
|
-
} catch {
|
|
191
|
-
return {};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export class ChangelogCommands {
|
|
196
|
-
public readonly changelog = $command({
|
|
197
|
-
name: "changelog",
|
|
198
|
-
description: "Generate CHANGELOG.md from git commits",
|
|
199
|
-
flags: t.object({
|
|
200
|
-
release: t.optional(
|
|
201
|
-
t.boolean({
|
|
202
|
-
when: ["--release", "-r"],
|
|
203
|
-
description:
|
|
204
|
-
"Output release notes for the latest version only (for GitHub Release)",
|
|
205
|
-
}),
|
|
206
|
-
),
|
|
207
|
-
preview: t.optional(
|
|
208
|
-
t.boolean({
|
|
209
|
-
when: ["--preview", "-p"],
|
|
210
|
-
description: "Preview unreleased changes (commits since last tag)",
|
|
211
|
-
}),
|
|
212
|
-
),
|
|
213
|
-
output: t.optional(
|
|
214
|
-
t.string({
|
|
215
|
-
when: ["--output", "-o"],
|
|
216
|
-
description:
|
|
217
|
-
"Output file path (defaults to CHANGELOG.md, use - for stdout)",
|
|
218
|
-
}),
|
|
219
|
-
),
|
|
220
|
-
limit: t.optional(
|
|
221
|
-
t.number({
|
|
222
|
-
when: ["--limit", "-l"],
|
|
223
|
-
description: "Limit number of versions to include",
|
|
224
|
-
}),
|
|
225
|
-
),
|
|
226
|
-
}),
|
|
227
|
-
handler: async ({ flags, run, root }) => {
|
|
228
|
-
const config = await loadConfig(root);
|
|
229
|
-
|
|
230
|
-
const git = async (cmd: string) => {
|
|
231
|
-
const { stdout } = await execAsync(`git ${cmd}`, { cwd: root });
|
|
232
|
-
return stdout;
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// Get all tags sorted by version
|
|
236
|
-
const tagsOutput = await git("tag --sort=-version:refname");
|
|
237
|
-
const tags = tagsOutput
|
|
238
|
-
.trim()
|
|
239
|
-
.split("\n")
|
|
240
|
-
.filter((tag) => tag.match(/^\d+\.\d+\.\d+$/));
|
|
241
|
-
|
|
242
|
-
if (tags.length === 0) {
|
|
243
|
-
throw new Error("No version tags found");
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Preview mode: show unreleased changes
|
|
247
|
-
if (flags.preview) {
|
|
248
|
-
const latestTag = tags[0];
|
|
249
|
-
const commitsOutput = await git(`log ${latestTag}..HEAD --oneline`);
|
|
250
|
-
|
|
251
|
-
if (!commitsOutput.trim()) {
|
|
252
|
-
console.log("No unreleased changes since", latestTag);
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
const entry: ChangelogEntry = {
|
|
257
|
-
version: "Unreleased",
|
|
258
|
-
date: new Date().toISOString().split("T")[0],
|
|
259
|
-
features: [],
|
|
260
|
-
fixes: [],
|
|
261
|
-
docs: [],
|
|
262
|
-
improvements: [],
|
|
263
|
-
breaking: [],
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
for (const line of commitsOutput.trim().split("\n")) {
|
|
267
|
-
if (!line.trim()) continue;
|
|
268
|
-
const commit = parseCommit(line, config);
|
|
269
|
-
if (!commit) continue;
|
|
270
|
-
|
|
271
|
-
if (commit.breaking) entry.breaking.push(commit);
|
|
272
|
-
|
|
273
|
-
switch (commit.type) {
|
|
274
|
-
case "feat":
|
|
275
|
-
entry.features.push(commit);
|
|
276
|
-
break;
|
|
277
|
-
case "fix":
|
|
278
|
-
entry.fixes.push(commit);
|
|
279
|
-
break;
|
|
280
|
-
case "docs":
|
|
281
|
-
entry.docs.push(commit);
|
|
282
|
-
break;
|
|
283
|
-
case "refactor":
|
|
284
|
-
case "perf":
|
|
285
|
-
case "improve":
|
|
286
|
-
entry.improvements.push(commit);
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const hasCommits =
|
|
292
|
-
entry.features.length > 0 ||
|
|
293
|
-
entry.fixes.length > 0 ||
|
|
294
|
-
entry.breaking.length > 0;
|
|
295
|
-
|
|
296
|
-
if (!hasCommits) {
|
|
297
|
-
console.log("No public changes since", latestTag);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
console.log(`## [Unreleased] - since ${latestTag}\n`);
|
|
302
|
-
console.log(formatEntry(entry));
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const entries: ChangelogEntry[] = [];
|
|
307
|
-
const limit = flags.limit || (flags.release ? 1 : tags.length);
|
|
308
|
-
|
|
309
|
-
for (let i = 0; i < Math.min(limit, tags.length); i++) {
|
|
310
|
-
const tag = tags[i];
|
|
311
|
-
const prevTag = tags[i + 1];
|
|
312
|
-
|
|
313
|
-
// Get tag date
|
|
314
|
-
const dateOutput = await git(`log -1 --format=%ci ${tag}`);
|
|
315
|
-
const date = dateOutput.trim().split(" ")[0];
|
|
316
|
-
|
|
317
|
-
// Get commits between tags
|
|
318
|
-
const range = prevTag ? `${prevTag}..${tag}` : tag;
|
|
319
|
-
const commitsOutput = await git(`log ${range} --oneline`);
|
|
320
|
-
|
|
321
|
-
const entry: ChangelogEntry = {
|
|
322
|
-
version: tag,
|
|
323
|
-
date,
|
|
324
|
-
features: [],
|
|
325
|
-
fixes: [],
|
|
326
|
-
docs: [],
|
|
327
|
-
improvements: [],
|
|
328
|
-
breaking: [],
|
|
329
|
-
};
|
|
330
|
-
|
|
331
|
-
for (const line of commitsOutput.trim().split("\n")) {
|
|
332
|
-
if (!line.trim()) continue;
|
|
333
|
-
const commit = parseCommit(line, config);
|
|
334
|
-
if (!commit) continue;
|
|
335
|
-
|
|
336
|
-
if (commit.breaking) entry.breaking.push(commit);
|
|
337
|
-
|
|
338
|
-
switch (commit.type) {
|
|
339
|
-
case "feat":
|
|
340
|
-
entry.features.push(commit);
|
|
341
|
-
break;
|
|
342
|
-
case "fix":
|
|
343
|
-
entry.fixes.push(commit);
|
|
344
|
-
break;
|
|
345
|
-
case "docs":
|
|
346
|
-
entry.docs.push(commit);
|
|
347
|
-
break;
|
|
348
|
-
case "refactor":
|
|
349
|
-
case "perf":
|
|
350
|
-
case "improve":
|
|
351
|
-
entry.improvements.push(commit);
|
|
352
|
-
break;
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
// Only add entry if it has any commits
|
|
357
|
-
const hasCommits =
|
|
358
|
-
entry.features.length > 0 ||
|
|
359
|
-
entry.fixes.length > 0 ||
|
|
360
|
-
entry.breaking.length > 0;
|
|
361
|
-
|
|
362
|
-
if (hasCommits) {
|
|
363
|
-
entries.push(entry);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (entries.length === 0) {
|
|
368
|
-
console.log("No public commits found");
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
let output: string;
|
|
373
|
-
if (flags.release) {
|
|
374
|
-
output = formatEntry(entries[0]);
|
|
375
|
-
} else {
|
|
376
|
-
output = generateChangelog(entries);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
const outputPath = flags.output ?? "CHANGELOG.md";
|
|
380
|
-
if (outputPath === "-") {
|
|
381
|
-
console.log(output);
|
|
382
|
-
} else {
|
|
383
|
-
await run(`Writing ${outputPath}`, () =>
|
|
384
|
-
writeFile(join(root, outputPath), output, "utf8"),
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
},
|
|
388
|
-
});
|
|
389
|
-
}
|