@lusipad/pmspec 1.0.0
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 +306 -0
- package/README.zh.md +304 -0
- package/bin/pmspec.js +5 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +39 -0
- package/dist/commands/analyze.d.ts +4 -0
- package/dist/commands/analyze.js +240 -0
- package/dist/commands/breakdown.d.ts +4 -0
- package/dist/commands/breakdown.js +194 -0
- package/dist/commands/create.d.ts +4 -0
- package/dist/commands/create.js +529 -0
- package/dist/commands/history.d.ts +4 -0
- package/dist/commands/history.js +213 -0
- package/dist/commands/import.d.ts +4 -0
- package/dist/commands/import.js +196 -0
- package/dist/commands/index-legacy.d.ts +4 -0
- package/dist/commands/index-legacy.js +27 -0
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.js +60 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.js +127 -0
- package/dist/commands/search.d.ts +7 -0
- package/dist/commands/search.js +183 -0
- package/dist/commands/serve.d.ts +3 -0
- package/dist/commands/serve.js +68 -0
- package/dist/commands/show.d.ts +3 -0
- package/dist/commands/show.js +152 -0
- package/dist/commands/simple.d.ts +7 -0
- package/dist/commands/simple.js +360 -0
- package/dist/commands/update.d.ts +4 -0
- package/dist/commands/update.js +247 -0
- package/dist/commands/validate.d.ts +3 -0
- package/dist/commands/validate.js +74 -0
- package/dist/core/changelog-service.d.ts +88 -0
- package/dist/core/changelog-service.js +208 -0
- package/dist/core/changelog.d.ts +113 -0
- package/dist/core/changelog.js +147 -0
- package/dist/core/importers.d.ts +343 -0
- package/dist/core/importers.js +715 -0
- package/dist/core/parser.d.ts +50 -0
- package/dist/core/parser.js +246 -0
- package/dist/core/project.d.ts +155 -0
- package/dist/core/project.js +138 -0
- package/dist/core/search.d.ts +119 -0
- package/dist/core/search.js +299 -0
- package/dist/core/simple-model.d.ts +54 -0
- package/dist/core/simple-model.js +20 -0
- package/dist/core/team.d.ts +41 -0
- package/dist/core/team.js +57 -0
- package/dist/core/workload.d.ts +49 -0
- package/dist/core/workload.js +116 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +11 -0
- package/dist/utils/csv-handler.d.ts +15 -0
- package/dist/utils/csv-handler.js +224 -0
- package/dist/utils/markdown.d.ts +43 -0
- package/dist/utils/markdown.js +202 -0
- package/dist/utils/validation.d.ts +35 -0
- package/dist/utils/validation.js +178 -0
- package/package.json +71 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ChangelogEntry, ChangelogFile, ChangelogQueryOptions, ChangelogEntityType } from './changelog.js';
|
|
2
|
+
/**
|
|
3
|
+
* Service for managing changelog operations
|
|
4
|
+
*/
|
|
5
|
+
export declare class ChangelogService {
|
|
6
|
+
private filePath;
|
|
7
|
+
constructor(basePath?: string);
|
|
8
|
+
/**
|
|
9
|
+
* Initialize the changelog file if it doesn't exist
|
|
10
|
+
*/
|
|
11
|
+
initialize(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Read the changelog file
|
|
14
|
+
*/
|
|
15
|
+
read(): Promise<ChangelogFile>;
|
|
16
|
+
/**
|
|
17
|
+
* Write the changelog file
|
|
18
|
+
*/
|
|
19
|
+
write(data: ChangelogFile): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Add entries to the changelog
|
|
22
|
+
*/
|
|
23
|
+
addEntries(entries: ChangelogEntry[]): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Record a create action
|
|
26
|
+
*/
|
|
27
|
+
recordCreate(entityType: ChangelogEntityType, entityId: string, user?: string): Promise<ChangelogEntry>;
|
|
28
|
+
/**
|
|
29
|
+
* Record a delete action
|
|
30
|
+
*/
|
|
31
|
+
recordDelete(entityType: ChangelogEntityType, entityId: string, user?: string): Promise<ChangelogEntry>;
|
|
32
|
+
/**
|
|
33
|
+
* Record an update action for a single field
|
|
34
|
+
*/
|
|
35
|
+
recordUpdate(entityType: ChangelogEntityType, entityId: string, field: string, oldValue: unknown, newValue: unknown, user?: string): Promise<ChangelogEntry | null>;
|
|
36
|
+
/**
|
|
37
|
+
* Record multiple field updates at once
|
|
38
|
+
*/
|
|
39
|
+
recordUpdates(entityType: ChangelogEntityType, entityId: string, changes: Record<string, {
|
|
40
|
+
oldValue: unknown;
|
|
41
|
+
newValue: unknown;
|
|
42
|
+
}>, user?: string): Promise<ChangelogEntry[]>;
|
|
43
|
+
/**
|
|
44
|
+
* Query changelog entries
|
|
45
|
+
*/
|
|
46
|
+
query(options?: ChangelogQueryOptions): Promise<ChangelogEntry[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Get history for a specific entity
|
|
49
|
+
*/
|
|
50
|
+
getEntityHistory(entityId: string): Promise<ChangelogEntry[]>;
|
|
51
|
+
/**
|
|
52
|
+
* Get all entries (with optional pagination)
|
|
53
|
+
*/
|
|
54
|
+
getAll(limit?: number, offset?: number): Promise<{
|
|
55
|
+
entries: ChangelogEntry[];
|
|
56
|
+
total: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Get entries since a specific date
|
|
60
|
+
*/
|
|
61
|
+
getSince(since: string): Promise<ChangelogEntry[]>;
|
|
62
|
+
/**
|
|
63
|
+
* Get changelog summary statistics
|
|
64
|
+
*/
|
|
65
|
+
getStats(): Promise<{
|
|
66
|
+
totalEntries: number;
|
|
67
|
+
byEntityType: Record<string, number>;
|
|
68
|
+
byAction: Record<string, number>;
|
|
69
|
+
recentActivity: {
|
|
70
|
+
last24h: number;
|
|
71
|
+
last7d: number;
|
|
72
|
+
last30d: number;
|
|
73
|
+
};
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* Clear all changelog entries (use with caution)
|
|
77
|
+
*/
|
|
78
|
+
clear(): Promise<void>;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the default changelog service instance
|
|
82
|
+
*/
|
|
83
|
+
export declare function getChangelogService(): ChangelogService;
|
|
84
|
+
/**
|
|
85
|
+
* Reset the default service (mainly for testing)
|
|
86
|
+
*/
|
|
87
|
+
export declare function resetChangelogService(): void;
|
|
88
|
+
//# sourceMappingURL=changelog-service.d.ts.map
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { ChangelogFileSchema, createChangelogEntry, createUpdateEntries, filterChangelogEntries, } from './changelog.js';
|
|
4
|
+
const CHANGELOG_FILE = 'pmspace/changelog.json';
|
|
5
|
+
/**
|
|
6
|
+
* Service for managing changelog operations
|
|
7
|
+
*/
|
|
8
|
+
export class ChangelogService {
|
|
9
|
+
filePath;
|
|
10
|
+
constructor(basePath = process.cwd()) {
|
|
11
|
+
this.filePath = join(basePath, CHANGELOG_FILE);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Initialize the changelog file if it doesn't exist
|
|
15
|
+
*/
|
|
16
|
+
async initialize() {
|
|
17
|
+
try {
|
|
18
|
+
await readFile(this.filePath, 'utf-8');
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code === 'ENOENT') {
|
|
22
|
+
// Create directory and file
|
|
23
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
24
|
+
const initialData = {
|
|
25
|
+
version: '1.0',
|
|
26
|
+
lastUpdated: new Date().toISOString(),
|
|
27
|
+
entries: [],
|
|
28
|
+
};
|
|
29
|
+
await writeFile(this.filePath, JSON.stringify(initialData, null, 2));
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the changelog file
|
|
38
|
+
*/
|
|
39
|
+
async read() {
|
|
40
|
+
try {
|
|
41
|
+
const content = await readFile(this.filePath, 'utf-8');
|
|
42
|
+
const data = JSON.parse(content);
|
|
43
|
+
return ChangelogFileSchema.parse(data);
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
if (error.code === 'ENOENT') {
|
|
47
|
+
// Return empty changelog if file doesn't exist
|
|
48
|
+
return {
|
|
49
|
+
version: '1.0',
|
|
50
|
+
lastUpdated: new Date().toISOString(),
|
|
51
|
+
entries: [],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Write the changelog file
|
|
59
|
+
*/
|
|
60
|
+
async write(data) {
|
|
61
|
+
await mkdir(dirname(this.filePath), { recursive: true });
|
|
62
|
+
data.lastUpdated = new Date().toISOString();
|
|
63
|
+
await writeFile(this.filePath, JSON.stringify(data, null, 2));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Add entries to the changelog
|
|
67
|
+
*/
|
|
68
|
+
async addEntries(entries) {
|
|
69
|
+
if (entries.length === 0)
|
|
70
|
+
return;
|
|
71
|
+
const data = await this.read();
|
|
72
|
+
data.entries.push(...entries);
|
|
73
|
+
await this.write(data);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Record a create action
|
|
77
|
+
*/
|
|
78
|
+
async recordCreate(entityType, entityId, user) {
|
|
79
|
+
const entry = createChangelogEntry(entityType, entityId, 'create', { user });
|
|
80
|
+
await this.addEntries([entry]);
|
|
81
|
+
return entry;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Record a delete action
|
|
85
|
+
*/
|
|
86
|
+
async recordDelete(entityType, entityId, user) {
|
|
87
|
+
const entry = createChangelogEntry(entityType, entityId, 'delete', { user });
|
|
88
|
+
await this.addEntries([entry]);
|
|
89
|
+
return entry;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Record an update action for a single field
|
|
93
|
+
*/
|
|
94
|
+
async recordUpdate(entityType, entityId, field, oldValue, newValue, user) {
|
|
95
|
+
// Don't record if value didn't change
|
|
96
|
+
if (JSON.stringify(oldValue) === JSON.stringify(newValue)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const entry = createChangelogEntry(entityType, entityId, 'update', {
|
|
100
|
+
field,
|
|
101
|
+
oldValue,
|
|
102
|
+
newValue,
|
|
103
|
+
user,
|
|
104
|
+
});
|
|
105
|
+
await this.addEntries([entry]);
|
|
106
|
+
return entry;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Record multiple field updates at once
|
|
110
|
+
*/
|
|
111
|
+
async recordUpdates(entityType, entityId, changes, user) {
|
|
112
|
+
const entries = createUpdateEntries(entityType, entityId, changes, user);
|
|
113
|
+
await this.addEntries(entries);
|
|
114
|
+
return entries;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Query changelog entries
|
|
118
|
+
*/
|
|
119
|
+
async query(options = {}) {
|
|
120
|
+
const data = await this.read();
|
|
121
|
+
return filterChangelogEntries(data.entries, options);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get history for a specific entity
|
|
125
|
+
*/
|
|
126
|
+
async getEntityHistory(entityId) {
|
|
127
|
+
return this.query({ entityId });
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get all entries (with optional pagination)
|
|
131
|
+
*/
|
|
132
|
+
async getAll(limit, offset) {
|
|
133
|
+
const data = await this.read();
|
|
134
|
+
const total = data.entries.length;
|
|
135
|
+
const entries = filterChangelogEntries(data.entries, { limit, offset });
|
|
136
|
+
return { entries, total };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get entries since a specific date
|
|
140
|
+
*/
|
|
141
|
+
async getSince(since) {
|
|
142
|
+
return this.query({ since });
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Get changelog summary statistics
|
|
146
|
+
*/
|
|
147
|
+
async getStats() {
|
|
148
|
+
const data = await this.read();
|
|
149
|
+
const entries = data.entries;
|
|
150
|
+
const now = new Date();
|
|
151
|
+
const oneDayAgo = new Date(now.getTime() - 24 * 60 * 60 * 1000);
|
|
152
|
+
const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
153
|
+
const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
154
|
+
const byEntityType = {};
|
|
155
|
+
const byAction = {};
|
|
156
|
+
let last24h = 0;
|
|
157
|
+
let last7d = 0;
|
|
158
|
+
let last30d = 0;
|
|
159
|
+
for (const entry of entries) {
|
|
160
|
+
// Count by entity type
|
|
161
|
+
byEntityType[entry.entityType] = (byEntityType[entry.entityType] || 0) + 1;
|
|
162
|
+
// Count by action
|
|
163
|
+
byAction[entry.action] = (byAction[entry.action] || 0) + 1;
|
|
164
|
+
// Count recent activity
|
|
165
|
+
const entryDate = new Date(entry.timestamp);
|
|
166
|
+
if (entryDate >= oneDayAgo)
|
|
167
|
+
last24h++;
|
|
168
|
+
if (entryDate >= oneWeekAgo)
|
|
169
|
+
last7d++;
|
|
170
|
+
if (entryDate >= oneMonthAgo)
|
|
171
|
+
last30d++;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
totalEntries: entries.length,
|
|
175
|
+
byEntityType,
|
|
176
|
+
byAction,
|
|
177
|
+
recentActivity: { last24h, last7d, last30d },
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Clear all changelog entries (use with caution)
|
|
182
|
+
*/
|
|
183
|
+
async clear() {
|
|
184
|
+
await this.write({
|
|
185
|
+
version: '1.0',
|
|
186
|
+
lastUpdated: new Date().toISOString(),
|
|
187
|
+
entries: [],
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
// Singleton instance for convenience
|
|
192
|
+
let defaultService = null;
|
|
193
|
+
/**
|
|
194
|
+
* Get the default changelog service instance
|
|
195
|
+
*/
|
|
196
|
+
export function getChangelogService() {
|
|
197
|
+
if (!defaultService) {
|
|
198
|
+
defaultService = new ChangelogService();
|
|
199
|
+
}
|
|
200
|
+
return defaultService;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Reset the default service (mainly for testing)
|
|
204
|
+
*/
|
|
205
|
+
export function resetChangelogService() {
|
|
206
|
+
defaultService = null;
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=changelog-service.js.map
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Entity types that can be tracked in the changelog
|
|
4
|
+
*/
|
|
5
|
+
export declare const ChangelogEntityType: z.ZodEnum<{
|
|
6
|
+
epic: "epic";
|
|
7
|
+
feature: "feature";
|
|
8
|
+
milestone: "milestone";
|
|
9
|
+
story: "story";
|
|
10
|
+
}>;
|
|
11
|
+
export type ChangelogEntityType = z.infer<typeof ChangelogEntityType>;
|
|
12
|
+
/**
|
|
13
|
+
* Action types for changelog entries
|
|
14
|
+
*/
|
|
15
|
+
export declare const ChangelogAction: z.ZodEnum<{
|
|
16
|
+
create: "create";
|
|
17
|
+
update: "update";
|
|
18
|
+
delete: "delete";
|
|
19
|
+
}>;
|
|
20
|
+
export type ChangelogAction = z.infer<typeof ChangelogAction>;
|
|
21
|
+
/**
|
|
22
|
+
* Schema for a single changelog entry
|
|
23
|
+
*/
|
|
24
|
+
export declare const ChangelogEntrySchema: z.ZodObject<{
|
|
25
|
+
id: z.ZodString;
|
|
26
|
+
timestamp: z.ZodString;
|
|
27
|
+
entityType: z.ZodEnum<{
|
|
28
|
+
epic: "epic";
|
|
29
|
+
feature: "feature";
|
|
30
|
+
milestone: "milestone";
|
|
31
|
+
story: "story";
|
|
32
|
+
}>;
|
|
33
|
+
entityId: z.ZodString;
|
|
34
|
+
action: z.ZodEnum<{
|
|
35
|
+
create: "create";
|
|
36
|
+
update: "update";
|
|
37
|
+
delete: "delete";
|
|
38
|
+
}>;
|
|
39
|
+
field: z.ZodOptional<z.ZodString>;
|
|
40
|
+
oldValue: z.ZodOptional<z.ZodUnknown>;
|
|
41
|
+
newValue: z.ZodOptional<z.ZodUnknown>;
|
|
42
|
+
user: z.ZodOptional<z.ZodString>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type ChangelogEntry = z.infer<typeof ChangelogEntrySchema>;
|
|
45
|
+
/**
|
|
46
|
+
* Schema for the changelog file structure
|
|
47
|
+
*/
|
|
48
|
+
export declare const ChangelogFileSchema: z.ZodObject<{
|
|
49
|
+
version: z.ZodDefault<z.ZodString>;
|
|
50
|
+
lastUpdated: z.ZodOptional<z.ZodString>;
|
|
51
|
+
entries: z.ZodArray<z.ZodObject<{
|
|
52
|
+
id: z.ZodString;
|
|
53
|
+
timestamp: z.ZodString;
|
|
54
|
+
entityType: z.ZodEnum<{
|
|
55
|
+
epic: "epic";
|
|
56
|
+
feature: "feature";
|
|
57
|
+
milestone: "milestone";
|
|
58
|
+
story: "story";
|
|
59
|
+
}>;
|
|
60
|
+
entityId: z.ZodString;
|
|
61
|
+
action: z.ZodEnum<{
|
|
62
|
+
create: "create";
|
|
63
|
+
update: "update";
|
|
64
|
+
delete: "delete";
|
|
65
|
+
}>;
|
|
66
|
+
field: z.ZodOptional<z.ZodString>;
|
|
67
|
+
oldValue: z.ZodOptional<z.ZodUnknown>;
|
|
68
|
+
newValue: z.ZodOptional<z.ZodUnknown>;
|
|
69
|
+
user: z.ZodOptional<z.ZodString>;
|
|
70
|
+
}, z.core.$strip>>;
|
|
71
|
+
}, z.core.$strip>;
|
|
72
|
+
export type ChangelogFile = z.infer<typeof ChangelogFileSchema>;
|
|
73
|
+
/**
|
|
74
|
+
* Options for querying changelog entries
|
|
75
|
+
*/
|
|
76
|
+
export interface ChangelogQueryOptions {
|
|
77
|
+
entityId?: string;
|
|
78
|
+
entityType?: ChangelogEntityType;
|
|
79
|
+
action?: ChangelogAction;
|
|
80
|
+
since?: string;
|
|
81
|
+
until?: string;
|
|
82
|
+
limit?: number;
|
|
83
|
+
offset?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generate a unique ID for a changelog entry
|
|
87
|
+
*/
|
|
88
|
+
export declare function generateChangelogId(): string;
|
|
89
|
+
/**
|
|
90
|
+
* Create a new changelog entry
|
|
91
|
+
*/
|
|
92
|
+
export declare function createChangelogEntry(entityType: ChangelogEntityType, entityId: string, action: ChangelogAction, options?: {
|
|
93
|
+
field?: string;
|
|
94
|
+
oldValue?: unknown;
|
|
95
|
+
newValue?: unknown;
|
|
96
|
+
user?: string;
|
|
97
|
+
}): ChangelogEntry;
|
|
98
|
+
/**
|
|
99
|
+
* Create multiple changelog entries for a batch of field updates
|
|
100
|
+
*/
|
|
101
|
+
export declare function createUpdateEntries(entityType: ChangelogEntityType, entityId: string, changes: Record<string, {
|
|
102
|
+
oldValue: unknown;
|
|
103
|
+
newValue: unknown;
|
|
104
|
+
}>, user?: string): ChangelogEntry[];
|
|
105
|
+
/**
|
|
106
|
+
* Filter changelog entries based on query options
|
|
107
|
+
*/
|
|
108
|
+
export declare function filterChangelogEntries(entries: ChangelogEntry[], options: ChangelogQueryOptions): ChangelogEntry[];
|
|
109
|
+
/**
|
|
110
|
+
* Format a changelog entry for display
|
|
111
|
+
*/
|
|
112
|
+
export declare function formatChangelogEntry(entry: ChangelogEntry): string;
|
|
113
|
+
//# sourceMappingURL=changelog.d.ts.map
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Entity types that can be tracked in the changelog
|
|
4
|
+
*/
|
|
5
|
+
export const ChangelogEntityType = z.enum(['epic', 'feature', 'milestone', 'story']);
|
|
6
|
+
/**
|
|
7
|
+
* Action types for changelog entries
|
|
8
|
+
*/
|
|
9
|
+
export const ChangelogAction = z.enum(['create', 'update', 'delete']);
|
|
10
|
+
/**
|
|
11
|
+
* Schema for a single changelog entry
|
|
12
|
+
*/
|
|
13
|
+
export const ChangelogEntrySchema = z.object({
|
|
14
|
+
id: z.string(),
|
|
15
|
+
timestamp: z.string(), // ISO datetime
|
|
16
|
+
entityType: ChangelogEntityType,
|
|
17
|
+
entityId: z.string(),
|
|
18
|
+
action: ChangelogAction,
|
|
19
|
+
field: z.string().optional(),
|
|
20
|
+
oldValue: z.unknown().optional(),
|
|
21
|
+
newValue: z.unknown().optional(),
|
|
22
|
+
user: z.string().optional(),
|
|
23
|
+
});
|
|
24
|
+
/**
|
|
25
|
+
* Schema for the changelog file structure
|
|
26
|
+
*/
|
|
27
|
+
export const ChangelogFileSchema = z.object({
|
|
28
|
+
version: z.string().default('1.0'),
|
|
29
|
+
lastUpdated: z.string().optional(),
|
|
30
|
+
entries: z.array(ChangelogEntrySchema),
|
|
31
|
+
});
|
|
32
|
+
/**
|
|
33
|
+
* Generate a unique ID for a changelog entry
|
|
34
|
+
*/
|
|
35
|
+
export function generateChangelogId() {
|
|
36
|
+
const timestamp = Date.now().toString(36);
|
|
37
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
38
|
+
return `CHG-${timestamp}-${random}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create a new changelog entry
|
|
42
|
+
*/
|
|
43
|
+
export function createChangelogEntry(entityType, entityId, action, options) {
|
|
44
|
+
return ChangelogEntrySchema.parse({
|
|
45
|
+
id: generateChangelogId(),
|
|
46
|
+
timestamp: new Date().toISOString(),
|
|
47
|
+
entityType,
|
|
48
|
+
entityId,
|
|
49
|
+
action,
|
|
50
|
+
field: options?.field,
|
|
51
|
+
oldValue: options?.oldValue,
|
|
52
|
+
newValue: options?.newValue,
|
|
53
|
+
user: options?.user,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Create multiple changelog entries for a batch of field updates
|
|
58
|
+
*/
|
|
59
|
+
export function createUpdateEntries(entityType, entityId, changes, user) {
|
|
60
|
+
const entries = [];
|
|
61
|
+
const timestamp = new Date().toISOString();
|
|
62
|
+
for (const [field, { oldValue, newValue }] of Object.entries(changes)) {
|
|
63
|
+
// Only create entry if value actually changed
|
|
64
|
+
if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
|
|
65
|
+
entries.push(ChangelogEntrySchema.parse({
|
|
66
|
+
id: generateChangelogId(),
|
|
67
|
+
timestamp,
|
|
68
|
+
entityType,
|
|
69
|
+
entityId,
|
|
70
|
+
action: 'update',
|
|
71
|
+
field,
|
|
72
|
+
oldValue,
|
|
73
|
+
newValue,
|
|
74
|
+
user,
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return entries;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Filter changelog entries based on query options
|
|
82
|
+
*/
|
|
83
|
+
export function filterChangelogEntries(entries, options) {
|
|
84
|
+
let filtered = [...entries];
|
|
85
|
+
if (options.entityId) {
|
|
86
|
+
filtered = filtered.filter(e => e.entityId === options.entityId);
|
|
87
|
+
}
|
|
88
|
+
if (options.entityType) {
|
|
89
|
+
filtered = filtered.filter(e => e.entityType === options.entityType);
|
|
90
|
+
}
|
|
91
|
+
if (options.action) {
|
|
92
|
+
filtered = filtered.filter(e => e.action === options.action);
|
|
93
|
+
}
|
|
94
|
+
if (options.since) {
|
|
95
|
+
const sinceDate = new Date(options.since);
|
|
96
|
+
filtered = filtered.filter(e => new Date(e.timestamp) >= sinceDate);
|
|
97
|
+
}
|
|
98
|
+
if (options.until) {
|
|
99
|
+
const untilDate = new Date(options.until);
|
|
100
|
+
filtered = filtered.filter(e => new Date(e.timestamp) <= untilDate);
|
|
101
|
+
}
|
|
102
|
+
// Sort by timestamp descending (newest first)
|
|
103
|
+
filtered.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
104
|
+
// Apply pagination
|
|
105
|
+
if (options.offset) {
|
|
106
|
+
filtered = filtered.slice(options.offset);
|
|
107
|
+
}
|
|
108
|
+
if (options.limit) {
|
|
109
|
+
filtered = filtered.slice(0, options.limit);
|
|
110
|
+
}
|
|
111
|
+
return filtered;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format a changelog entry for display
|
|
115
|
+
*/
|
|
116
|
+
export function formatChangelogEntry(entry) {
|
|
117
|
+
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
118
|
+
const user = entry.user ? ` by ${entry.user}` : '';
|
|
119
|
+
switch (entry.action) {
|
|
120
|
+
case 'create':
|
|
121
|
+
return `[${timestamp}] Created ${entry.entityType} ${entry.entityId}${user}`;
|
|
122
|
+
case 'delete':
|
|
123
|
+
return `[${timestamp}] Deleted ${entry.entityType} ${entry.entityId}${user}`;
|
|
124
|
+
case 'update':
|
|
125
|
+
if (entry.field) {
|
|
126
|
+
const oldVal = formatValue(entry.oldValue);
|
|
127
|
+
const newVal = formatValue(entry.newValue);
|
|
128
|
+
return `[${timestamp}] Updated ${entry.entityType} ${entry.entityId} - ${entry.field}: ${oldVal} → ${newVal}${user}`;
|
|
129
|
+
}
|
|
130
|
+
return `[${timestamp}] Updated ${entry.entityType} ${entry.entityId}${user}`;
|
|
131
|
+
default:
|
|
132
|
+
return `[${timestamp}] ${entry.action} ${entry.entityType} ${entry.entityId}${user}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Format a value for display
|
|
137
|
+
*/
|
|
138
|
+
function formatValue(value) {
|
|
139
|
+
if (value === undefined || value === null) {
|
|
140
|
+
return '(empty)';
|
|
141
|
+
}
|
|
142
|
+
if (typeof value === 'object') {
|
|
143
|
+
return JSON.stringify(value);
|
|
144
|
+
}
|
|
145
|
+
return String(value);
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=changelog.js.map
|