alepha 0.14.0 → 0.14.1
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/dist/api/audits/index.d.ts +417 -338
- package/dist/api/audits/index.d.ts.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/jobs/index.d.ts +236 -157
- package/dist/api/jobs/index.d.ts.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/users/index.d.ts +833 -830
- package/dist/api/users/index.d.ts.map +1 -1
- package/dist/cli/index.d.ts +212 -29
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +320 -219
- package/dist/cli/index.js.map +1 -1
- package/dist/command/index.d.ts +206 -9
- 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.js.map +1 -1
- package/dist/core/index.native.js.map +1 -1
- package/dist/file/index.d.ts.map +1 -1
- package/dist/file/index.js.map +1 -1
- package/dist/orm/index.d.ts +180 -126
- package/dist/orm/index.d.ts.map +1 -1
- package/dist/orm/index.js +486 -512
- 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/scheduler/index.d.ts +6 -6
- package/dist/security/index.d.ts +28 -28
- package/dist/security/index.d.ts.map +1 -1
- package/dist/server/auth/index.d.ts +155 -155
- 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/health/index.d.ts +17 -17
- package/dist/server/helmet/index.d.ts +4 -1
- package/dist/server/helmet/index.d.ts.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/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/package.json +3 -3
- package/src/cli/apps/AlephaCli.ts +8 -3
- package/src/cli/apps/AlephaPackageBuilderCli.ts +3 -0
- package/src/cli/atoms/changelogOptions.ts +45 -0
- package/src/cli/commands/ChangelogCommands.ts +187 -317
- package/src/cli/commands/DeployCommands.ts +118 -0
- package/src/cli/commands/DrizzleCommands.ts +28 -8
- package/src/cli/commands/ViteCommands.ts +23 -9
- package/src/cli/defineConfig.ts +15 -0
- package/src/cli/index.ts +3 -0
- 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 +1 -1
- package/src/file/providers/NodeFileSystemProvider.ts +3 -1
- package/src/orm/index.ts +8 -4
- package/src/orm/interfaces/PgQueryWhere.ts +1 -26
- 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/orm/services/PgJsonQueryManager.ts +0 -511
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { exec } from "node:child_process";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { join } from "node:path";
|
|
4
2
|
import { promisify } from "node:util";
|
|
5
|
-
import { t } from "alepha";
|
|
3
|
+
import { $inject, $use, t } from "alepha";
|
|
6
4
|
import { $command } from "alepha/command";
|
|
5
|
+
import { $logger } from "alepha/logger";
|
|
6
|
+
import { changelogOptions } from "../atoms/changelogOptions.ts";
|
|
7
|
+
import { GitMessageParser } from "../services/GitMessageParser.ts";
|
|
8
|
+
|
|
9
|
+
export {
|
|
10
|
+
type ChangelogOptions,
|
|
11
|
+
changelogOptions,
|
|
12
|
+
DEFAULT_IGNORE,
|
|
13
|
+
} from "../atoms/changelogOptions.ts";
|
|
14
|
+
export { GitMessageParser } from "../services/GitMessageParser.ts";
|
|
7
15
|
|
|
8
16
|
const execAsync = promisify(exec);
|
|
9
17
|
|
|
10
|
-
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// GIT PROVIDER
|
|
20
|
+
// =============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Git provider for executing git commands.
|
|
24
|
+
* Can be substituted in tests with a mock implementation.
|
|
25
|
+
*/
|
|
26
|
+
export class GitProvider {
|
|
27
|
+
async exec(cmd: string, cwd: string): Promise<string> {
|
|
28
|
+
const { stdout } = await execAsync(`git ${cmd}`, { cwd });
|
|
29
|
+
return stdout;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// =============================================================================
|
|
34
|
+
// TYPES
|
|
35
|
+
// =============================================================================
|
|
36
|
+
|
|
37
|
+
export interface Commit {
|
|
11
38
|
hash: string;
|
|
12
39
|
type: string;
|
|
13
40
|
scope: string | null;
|
|
@@ -16,374 +43,217 @@ interface Commit {
|
|
|
16
43
|
}
|
|
17
44
|
|
|
18
45
|
interface ChangelogEntry {
|
|
19
|
-
version: string;
|
|
20
|
-
date: string;
|
|
21
46
|
features: Commit[];
|
|
22
47
|
fixes: Commit[];
|
|
23
|
-
docs: Commit[];
|
|
24
|
-
improvements: Commit[];
|
|
25
48
|
breaking: Commit[];
|
|
26
49
|
}
|
|
27
50
|
|
|
28
|
-
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// CHANGELOG COMMANDS
|
|
53
|
+
// =============================================================================
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Changelog command for generating release notes from git commits.
|
|
57
|
+
*
|
|
58
|
+
* Usage:
|
|
59
|
+
* - `alepha changelog` - Show unreleased changes since latest tag to HEAD
|
|
60
|
+
* - `alepha changelog --from=1.0.0` - Show changes from version to HEAD
|
|
61
|
+
* - `alepha changelog --from=1.0.0 --to=1.1.0` - Show changes between two refs
|
|
62
|
+
* - `alepha changelog | tee -a CHANGELOG.md` - Append to file
|
|
63
|
+
*/
|
|
64
|
+
export class ChangelogCommands {
|
|
65
|
+
protected readonly log = $logger();
|
|
66
|
+
protected readonly git = $inject(GitProvider);
|
|
67
|
+
protected readonly parser = $inject(GitMessageParser);
|
|
68
|
+
protected readonly config = $use(changelogOptions);
|
|
69
|
+
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// FORMATTING
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
|
|
29
74
|
/**
|
|
30
|
-
*
|
|
75
|
+
* Format a single commit line.
|
|
76
|
+
* Example: `- **cli**: add new command (\`abc1234\`)`
|
|
31
77
|
*/
|
|
32
|
-
|
|
78
|
+
protected formatCommit(commit: Commit): string {
|
|
79
|
+
return `- **${commit.scope}**: ${commit.description} (\`${commit.hash}\`)`;
|
|
80
|
+
}
|
|
81
|
+
|
|
33
82
|
/**
|
|
34
|
-
*
|
|
35
|
-
* If not provided, all non-ignored scopes are included
|
|
83
|
+
* Format the changelog entry with sections.
|
|
36
84
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
85
|
+
protected formatEntry(entry: ChangelogEntry): string {
|
|
86
|
+
const sections: string[] = [];
|
|
39
87
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
88
|
+
if (entry.breaking.length > 0) {
|
|
89
|
+
sections.push("### Breaking Changes\n");
|
|
90
|
+
for (const commit of entry.breaking) {
|
|
91
|
+
sections.push(this.formatCommit(commit));
|
|
73
92
|
}
|
|
93
|
+
sections.push("");
|
|
74
94
|
}
|
|
75
95
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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;
|
|
96
|
+
if (entry.features.length > 0) {
|
|
97
|
+
sections.push("### Features\n");
|
|
98
|
+
for (const commit of entry.features) {
|
|
99
|
+
sections.push(this.formatCommit(commit));
|
|
100
|
+
}
|
|
101
|
+
sections.push("");
|
|
106
102
|
}
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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";
|
|
104
|
+
if (entry.fixes.length > 0) {
|
|
105
|
+
sections.push("### Bug Fixes\n");
|
|
106
|
+
for (const commit of entry.fixes) {
|
|
107
|
+
sections.push(this.formatCommit(commit));
|
|
108
|
+
}
|
|
109
|
+
sections.push("");
|
|
123
110
|
}
|
|
124
111
|
|
|
125
|
-
return
|
|
126
|
-
hash: hash.substring(0, 8),
|
|
127
|
-
type,
|
|
128
|
-
scope: module,
|
|
129
|
-
description: description.trim(),
|
|
130
|
-
breaking,
|
|
131
|
-
};
|
|
112
|
+
return sections.join("\n");
|
|
132
113
|
}
|
|
133
114
|
|
|
134
|
-
|
|
135
|
-
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
116
|
+
// PARSING
|
|
117
|
+
// ---------------------------------------------------------------------------
|
|
136
118
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Parse git log output into a changelog entry.
|
|
121
|
+
*/
|
|
122
|
+
protected parseCommits(commitsOutput: string): ChangelogEntry {
|
|
123
|
+
const entry: ChangelogEntry = {
|
|
124
|
+
features: [],
|
|
125
|
+
fixes: [],
|
|
126
|
+
breaking: [],
|
|
127
|
+
};
|
|
146
128
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
output += formatEntry(entry);
|
|
150
|
-
}
|
|
129
|
+
for (const line of commitsOutput.trim().split("\n")) {
|
|
130
|
+
if (!line.trim()) continue;
|
|
151
131
|
|
|
152
|
-
|
|
153
|
-
|
|
132
|
+
const commit = this.parser.parseCommit(line, this.config);
|
|
133
|
+
if (!commit) {
|
|
134
|
+
this.log.trace("Skipping commit", { line });
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
154
137
|
|
|
155
|
-
|
|
156
|
-
let output = "";
|
|
138
|
+
this.log.trace("Parsed commit", { commit });
|
|
157
139
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
140
|
+
// Categorize commit
|
|
141
|
+
if (commit.breaking) {
|
|
142
|
+
entry.breaking.push(commit);
|
|
143
|
+
}
|
|
144
|
+
if (commit.type === "feat") {
|
|
145
|
+
entry.features.push(commit);
|
|
146
|
+
} else if (commit.type === "fix") {
|
|
147
|
+
entry.fixes.push(commit);
|
|
148
|
+
}
|
|
162
149
|
}
|
|
163
|
-
output += "\n";
|
|
164
|
-
}
|
|
165
150
|
|
|
166
|
-
|
|
167
|
-
output += "### Features\n\n";
|
|
168
|
-
for (const commit of entry.features) {
|
|
169
|
-
output += `${formatCommit(commit)}\n`;
|
|
170
|
-
}
|
|
171
|
-
output += "\n";
|
|
151
|
+
return entry;
|
|
172
152
|
}
|
|
173
153
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Check if entry has any public commits.
|
|
156
|
+
*/
|
|
157
|
+
protected hasChanges(entry: ChangelogEntry): boolean {
|
|
158
|
+
return (
|
|
159
|
+
entry.features.length > 0 ||
|
|
160
|
+
entry.fixes.length > 0 ||
|
|
161
|
+
entry.breaking.length > 0
|
|
162
|
+
);
|
|
180
163
|
}
|
|
181
164
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
async
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Get the latest version tag.
|
|
167
|
+
*/
|
|
168
|
+
protected async getLatestTag(
|
|
169
|
+
git: (cmd: string) => Promise<string>,
|
|
170
|
+
): Promise<string | null> {
|
|
171
|
+
const tagsOutput = await git("tag --sort=-version:refname");
|
|
172
|
+
const tags = tagsOutput
|
|
173
|
+
.trim()
|
|
174
|
+
.split("\n")
|
|
175
|
+
.filter((tag) => tag.match(/^\d+\.\d+\.\d+$/));
|
|
176
|
+
|
|
177
|
+
return tags[0] || null;
|
|
192
178
|
}
|
|
193
|
-
}
|
|
194
179
|
|
|
195
|
-
|
|
180
|
+
// ---------------------------------------------------------------------------
|
|
181
|
+
// COMMAND
|
|
182
|
+
// ---------------------------------------------------------------------------
|
|
183
|
+
|
|
196
184
|
public readonly changelog = $command({
|
|
197
185
|
name: "changelog",
|
|
198
|
-
description:
|
|
186
|
+
description:
|
|
187
|
+
"Generate changelog from conventional commits (outputs to stdout)",
|
|
199
188
|
flags: t.object({
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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(
|
|
189
|
+
/**
|
|
190
|
+
* Show changes from this ref (tag, commit, branch).
|
|
191
|
+
* Defaults to the latest version tag.
|
|
192
|
+
* Example: --from=1.0.0
|
|
193
|
+
*/
|
|
194
|
+
from: t.optional(
|
|
214
195
|
t.string({
|
|
215
|
-
|
|
216
|
-
description:
|
|
217
|
-
"Output file path (defaults to CHANGELOG.md, use - for stdout)",
|
|
196
|
+
aliases: ["f"],
|
|
197
|
+
description: "Starting ref (default: latest tag)",
|
|
218
198
|
}),
|
|
219
199
|
),
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Show changes up to this ref (tag, commit, branch).
|
|
202
|
+
* Defaults to HEAD.
|
|
203
|
+
* Example: --to=main
|
|
204
|
+
*/
|
|
205
|
+
to: t.optional(
|
|
206
|
+
t.string({
|
|
207
|
+
aliases: ["t"],
|
|
208
|
+
description: "Ending ref (default: HEAD)",
|
|
224
209
|
}),
|
|
225
210
|
),
|
|
226
211
|
}),
|
|
227
|
-
handler: async ({ flags,
|
|
228
|
-
const
|
|
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`);
|
|
212
|
+
handler: async ({ flags, root }) => {
|
|
213
|
+
const git = (cmd: string) => this.git.exec(cmd, root);
|
|
250
214
|
|
|
251
|
-
|
|
252
|
-
|
|
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;
|
|
215
|
+
// Determine the starting point
|
|
216
|
+
let fromRef: string;
|
|
295
217
|
|
|
296
|
-
|
|
297
|
-
|
|
218
|
+
if (flags.from) {
|
|
219
|
+
// User specified a ref
|
|
220
|
+
fromRef = flags.from;
|
|
221
|
+
this.log.debug("Using specified from ref", { from: fromRef });
|
|
222
|
+
} else {
|
|
223
|
+
// Use latest tag
|
|
224
|
+
const latestTag = await this.getLatestTag(git);
|
|
225
|
+
if (!latestTag) {
|
|
226
|
+
process.stdout.write("No version tags found in repository\n");
|
|
298
227
|
return;
|
|
299
228
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
console.log(formatEntry(entry));
|
|
303
|
-
return;
|
|
229
|
+
fromRef = latestTag;
|
|
230
|
+
this.log.debug("Using latest tag", { from: fromRef });
|
|
304
231
|
}
|
|
305
232
|
|
|
306
|
-
|
|
307
|
-
const
|
|
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
|
-
}
|
|
233
|
+
// Determine the ending point
|
|
234
|
+
const toRef = flags.to || "HEAD";
|
|
235
|
+
this.log.debug("Using to ref", { to: toRef });
|
|
355
236
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
entry.features.length > 0 ||
|
|
359
|
-
entry.fixes.length > 0 ||
|
|
360
|
-
entry.breaking.length > 0;
|
|
237
|
+
// Get commits in range
|
|
238
|
+
const commitsOutput = await git(`log ${fromRef}..${toRef} --oneline`);
|
|
361
239
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (entries.length === 0) {
|
|
368
|
-
console.log("No public commits found");
|
|
240
|
+
if (!commitsOutput.trim()) {
|
|
241
|
+
process.stdout.write(`No changes in range ${fromRef}..${toRef}\n`);
|
|
369
242
|
return;
|
|
370
243
|
}
|
|
371
244
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
output = formatEntry(entries[0]);
|
|
375
|
-
} else {
|
|
376
|
-
output = generateChangelog(entries);
|
|
377
|
-
}
|
|
245
|
+
// Parse and format
|
|
246
|
+
const entry = this.parseCommits(commitsOutput);
|
|
378
247
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
} else {
|
|
383
|
-
await run(`Writing ${outputPath}`, () =>
|
|
384
|
-
writeFile(join(root, outputPath), output, "utf8"),
|
|
248
|
+
if (!this.hasChanges(entry)) {
|
|
249
|
+
process.stdout.write(
|
|
250
|
+
`No public changes in range ${fromRef}..${toRef}\n`,
|
|
385
251
|
);
|
|
252
|
+
return;
|
|
386
253
|
}
|
|
254
|
+
|
|
255
|
+
// Output the formatted changelog (no header - caller adds it if needed)
|
|
256
|
+
process.stdout.write(this.formatEntry(entry));
|
|
387
257
|
},
|
|
388
258
|
});
|
|
389
259
|
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { $inject, AlephaError, t } from "alepha";
|
|
3
|
+
import { $command } from "alepha/command";
|
|
4
|
+
import { $logger } from "alepha/logger";
|
|
5
|
+
import { AlephaCliUtils } from "../services/AlephaCliUtils.ts";
|
|
6
|
+
|
|
7
|
+
export class DeployCommands {
|
|
8
|
+
protected readonly log = $logger();
|
|
9
|
+
protected readonly utils = $inject(AlephaCliUtils);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)
|
|
13
|
+
*
|
|
14
|
+
* Deploy command can be overridden by creating a alepha.config.ts in the project root:
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { defineConfig } from "alepha/cli";
|
|
18
|
+
*
|
|
19
|
+
* export default defineConfig({
|
|
20
|
+
* commands: {
|
|
21
|
+
* deploy: {
|
|
22
|
+
* handler: async ({ root, mode, flags }) => {
|
|
23
|
+
* // Custom deployment logic here
|
|
24
|
+
* },
|
|
25
|
+
* },
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
public readonly deploy = $command({
|
|
31
|
+
name: "deploy",
|
|
32
|
+
description:
|
|
33
|
+
"Deploy the project to a hosting platform (e.g., Vercel, Cloudflare, Surge)",
|
|
34
|
+
mode: true,
|
|
35
|
+
flags: t.object({
|
|
36
|
+
build: t.boolean({
|
|
37
|
+
description: "Build the project before deployment",
|
|
38
|
+
default: false,
|
|
39
|
+
}),
|
|
40
|
+
migrate: t.boolean({
|
|
41
|
+
description:
|
|
42
|
+
"Run database migrations before deployment (if applicable)",
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
}),
|
|
46
|
+
env: t.object({
|
|
47
|
+
VERCEL_TOKEN: t.optional(
|
|
48
|
+
t.text({
|
|
49
|
+
description: "Vercel API token (e.g., xxxxxxxxxxxxxxxxxxxx)",
|
|
50
|
+
}),
|
|
51
|
+
),
|
|
52
|
+
VERCEL_ORG_ID: t.optional(
|
|
53
|
+
t.text({
|
|
54
|
+
description: "Vercel organization ID (e.g., team_abc123...)",
|
|
55
|
+
}),
|
|
56
|
+
),
|
|
57
|
+
VERCEL_PROJECT_ID: t.optional(
|
|
58
|
+
t.text({ description: "Vercel project ID (e.g., prj_abc123...)" }),
|
|
59
|
+
),
|
|
60
|
+
CLOUDFLARE_API_TOKEN: t.optional(
|
|
61
|
+
t.text({
|
|
62
|
+
description: "Cloudflare API token (e.g., xxxx-xxxx-xxxx-xxxx)",
|
|
63
|
+
}),
|
|
64
|
+
),
|
|
65
|
+
CLOUDFLARE_ACCOUNT_ID: t.optional(
|
|
66
|
+
t.text({
|
|
67
|
+
description: "Cloudflare account ID (e.g., abc123def456...)",
|
|
68
|
+
}),
|
|
69
|
+
),
|
|
70
|
+
}),
|
|
71
|
+
handler: async ({ root, mode, flags }) => {
|
|
72
|
+
if (flags.build) {
|
|
73
|
+
await this.utils.exec("alepha build");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Vercel deployment
|
|
77
|
+
if (await this.utils.exists(root, "dist/vercel.json")) {
|
|
78
|
+
if (flags.migrate) {
|
|
79
|
+
this.log.debug("Running database migrations before deployment...");
|
|
80
|
+
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
81
|
+
}
|
|
82
|
+
await this.utils.ensureDependency(root, "vercel", { dev: true });
|
|
83
|
+
const command =
|
|
84
|
+
`vercel . --cwd=dist ${mode === "production" ? "--prod" : ""}`.trim();
|
|
85
|
+
this.log.debug(`Deploying to Vercel with command: ${command}`);
|
|
86
|
+
await this.utils.exec(command);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Cloudflare deployment
|
|
91
|
+
if (await this.utils.exists(root, "dist/wrangler.jsonc")) {
|
|
92
|
+
if (flags.migrate) {
|
|
93
|
+
this.log.debug("Running database migrations before deployment...");
|
|
94
|
+
await this.utils.exec(`alepha db migrate --mode=${mode}`);
|
|
95
|
+
}
|
|
96
|
+
await this.utils.ensureDependency(root, "wrangler", { dev: true });
|
|
97
|
+
const command =
|
|
98
|
+
`wrangler deploy ${mode === "production" ? "" : "--env preview"} --config=dist/wrangler.jsonc`.trim();
|
|
99
|
+
this.log.info(`Deploying to Cloudflare with command: ${command}`);
|
|
100
|
+
await this.utils.exec(command);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Surge deployment
|
|
105
|
+
if (await this.utils.exists(root, "dist/public/404.html")) {
|
|
106
|
+
await this.utils.ensureDependency(root, "surge", { dev: true });
|
|
107
|
+
const distPath = join(root, "dist/public");
|
|
108
|
+
this.log.debug(`Deploying to Surge from directory: ${distPath}`);
|
|
109
|
+
await this.utils.exec(`surge ${distPath}`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
throw new AlephaError(
|
|
114
|
+
"No deployment configuration found in the dist folder.",
|
|
115
|
+
);
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|