@critical-path/core 0.2.0 → 0.3.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/CHANGELOG.md +12 -0
- package/dist/engine/index.d.ts +19 -1
- package/dist/engine/index.d.ts.map +1 -1
- package/dist/engine/index.js +129 -6
- package/dist/engine/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +96 -1
- package/dist/index.test.js.map +1 -1
- package/dist/store/firebase.d.ts +19 -7
- package/dist/store/firebase.d.ts.map +1 -1
- package/dist/store/firebase.js +98 -14
- package/dist/store/firebase.js.map +1 -1
- package/dist/store/firebase.test.js +5 -2
- package/dist/store/firebase.test.js.map +1 -1
- package/dist/store/index.d.ts +34 -8
- package/dist/store/index.d.ts.map +1 -1
- package/dist/store/index.js +79 -12
- package/dist/store/index.js.map +1 -1
- package/dist/store/sqlite.d.ts +17 -4
- package/dist/store/sqlite.d.ts.map +1 -1
- package/dist/store/sqlite.js +179 -39
- package/dist/store/sqlite.js.map +1 -1
- package/dist/types/index.d.ts +70 -9
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/key.d.ts +14 -0
- package/dist/utils/key.d.ts.map +1 -0
- package/dist/utils/key.js +49 -0
- package/dist/utils/key.js.map +1 -0
- package/dist/utils/key.test.d.ts +2 -0
- package/dist/utils/key.test.d.ts.map +1 -0
- package/dist/utils/key.test.js +48 -0
- package/dist/utils/key.test.js.map +1 -0
- package/dist/utils/status.d.ts +14 -0
- package/dist/utils/status.d.ts.map +1 -0
- package/dist/utils/status.js +58 -0
- package/dist/utils/status.js.map +1 -0
- package/package.json +5 -1
- package/src/engine/index.ts +161 -8
- package/src/index.test.ts +113 -1
- package/src/index.ts +2 -0
- package/src/store/firebase.test.ts +8 -2
- package/src/store/firebase.ts +112 -18
- package/src/store/index.ts +113 -18
- package/src/store/sqlite.ts +277 -47
- package/src/types/index.ts +76 -10
- package/src/utils/index.ts +2 -0
- package/src/utils/key.test.ts +55 -0
- package/src/utils/key.ts +51 -0
- package/src/utils/status.ts +85 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateProjectKey, validateProjectKey, formatTaskKey } from './key.js';
|
|
3
|
+
|
|
4
|
+
describe('Project Key Utilities', () => {
|
|
5
|
+
describe('generateProjectKey', () => {
|
|
6
|
+
it('generates a key for a single word name', () => {
|
|
7
|
+
expect(generateProjectKey('Operative')).toBe('OPE');
|
|
8
|
+
expect(generateProjectKey('App')).toBe('APP');
|
|
9
|
+
expect(generateProjectKey('Go')).toBe('GO');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('generates a key for multi-word names', () => {
|
|
13
|
+
expect(generateProjectKey('Mobile App')).toBe('MA');
|
|
14
|
+
expect(generateProjectKey('Frontend Core Platform')).toBe('FCP');
|
|
15
|
+
expect(generateProjectKey('Very Long Project Name Here')).toBe('VLPN');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('handles special characters and whitespace', () => {
|
|
19
|
+
expect(generateProjectKey(' v2.0 ')).toBe('V20');
|
|
20
|
+
expect(generateProjectKey('Project #1')).toBe('P1');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns default PRJ fallback for empty input', () => {
|
|
24
|
+
expect(generateProjectKey('')).toBe('PRJ');
|
|
25
|
+
expect(generateProjectKey(' ')).toBe('PRJ');
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
describe('validateProjectKey', () => {
|
|
30
|
+
it('returns true for valid keys', () => {
|
|
31
|
+
expect(validateProjectKey('CP')).toBe(true);
|
|
32
|
+
expect(validateProjectKey('PROJ')).toBe(true);
|
|
33
|
+
expect(validateProjectKey('APP123')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('returns false for invalid keys', () => {
|
|
37
|
+
expect(validateProjectKey('A')).toBe(false); // too short
|
|
38
|
+
expect(validateProjectKey('TOOLONGKEY')).toBe(false); // too long
|
|
39
|
+
expect(validateProjectKey('proj')).toBe(false); // lowercase
|
|
40
|
+
expect(validateProjectKey('P-1')).toBe(false); // special char
|
|
41
|
+
expect(validateProjectKey('')).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('formatTaskKey', () => {
|
|
46
|
+
it('formats project key and task number', () => {
|
|
47
|
+
expect(formatTaskKey('MOB', 42)).toBe('MOB-42');
|
|
48
|
+
expect(formatTaskKey('op', 1)).toBe('OP-1');
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('uses fallback for missing project key', () => {
|
|
52
|
+
expect(formatTaskKey('', 10)).toBe('TASK-10');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
package/src/utils/key.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generates a 2-6 character uppercase project short code/key from a project name.
|
|
3
|
+
* e.g., "Mobile App" -> "MA", "Frontend Core" -> "FC", "Operative" -> "OPE".
|
|
4
|
+
*/
|
|
5
|
+
export function generateProjectKey(name: string): string {
|
|
6
|
+
if (!name || !name.trim()) {
|
|
7
|
+
return 'PRJ';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const words = name.trim().split(/\s+/).filter(Boolean);
|
|
11
|
+
let rawKey = '';
|
|
12
|
+
|
|
13
|
+
if (words.length === 1) {
|
|
14
|
+
const word = words[0].replace(/[^a-zA-Z0-9]/g, '');
|
|
15
|
+
if (word.length <= 3) {
|
|
16
|
+
rawKey = word.toUpperCase();
|
|
17
|
+
} else {
|
|
18
|
+
rawKey = word.substring(0, 3).toUpperCase();
|
|
19
|
+
}
|
|
20
|
+
} else {
|
|
21
|
+
rawKey = words
|
|
22
|
+
.map((w) => w.replace(/[^a-zA-Z0-9]/g, '')[0] || '')
|
|
23
|
+
.join('')
|
|
24
|
+
.substring(0, 4)
|
|
25
|
+
.toUpperCase();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Ensure key meets basic 2-6 char alphanumeric requirement
|
|
29
|
+
const cleanKey = rawKey.replace(/[^A-Z0-9]/g, '');
|
|
30
|
+
if (cleanKey.length >= 2 && cleanKey.length <= 6) {
|
|
31
|
+
return cleanKey;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return 'PRJ';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Validates whether a given key meets project key format rules (2-6 uppercase alphanumeric characters).
|
|
39
|
+
*/
|
|
40
|
+
export function validateProjectKey(key: string): boolean {
|
|
41
|
+
if (!key) return false;
|
|
42
|
+
return /^[A-Z0-9]{2,6}$/.test(key);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formats a task key using a project key and task index (e.g. "MOB-12").
|
|
47
|
+
*/
|
|
48
|
+
export function formatTaskKey(projectKey: string, taskNumber: number): string {
|
|
49
|
+
const prefix = projectKey ? projectKey.toUpperCase() : 'TASK';
|
|
50
|
+
return `${prefix}-${taskNumber}`;
|
|
51
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Task,
|
|
3
|
+
StatusDefinition,
|
|
4
|
+
CompletionState,
|
|
5
|
+
ExecutionState
|
|
6
|
+
} from '../types/index.js';
|
|
7
|
+
|
|
8
|
+
export const DEFAULT_STATUS_DEFINITIONS: Record<string, StatusDefinition> = {
|
|
9
|
+
backlog: { key: 'backlog', label: 'Backlog', completionState: 'not_done', executionState: 'inactive' },
|
|
10
|
+
todo: { key: 'todo', label: 'To Do', completionState: 'not_done', executionState: 'inactive' },
|
|
11
|
+
in_progress: { key: 'in_progress', label: 'In Progress', completionState: 'not_done', executionState: 'active' },
|
|
12
|
+
in_review: { key: 'in_review', label: 'In Review', completionState: 'not_done', executionState: 'active' },
|
|
13
|
+
done: { key: 'done', label: 'Done', completionState: 'done', executionState: 'inactive' },
|
|
14
|
+
canceled: { key: 'canceled', label: 'Canceled', completionState: 'done', executionState: 'inactive', isCancelled: true }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export interface TaskLifecycleState {
|
|
18
|
+
completionState: CompletionState;
|
|
19
|
+
executionState: ExecutionState;
|
|
20
|
+
isDone: boolean;
|
|
21
|
+
isActive: boolean;
|
|
22
|
+
isCancelled: boolean;
|
|
23
|
+
isOverdue: boolean;
|
|
24
|
+
isUpcoming: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function resolveStatusDefinition(
|
|
28
|
+
statusKey: string,
|
|
29
|
+
customDefinitions?: StatusDefinition[]
|
|
30
|
+
): StatusDefinition {
|
|
31
|
+
if (customDefinitions) {
|
|
32
|
+
const found = customDefinitions.find((def) => def.key === statusKey);
|
|
33
|
+
if (found) return found;
|
|
34
|
+
}
|
|
35
|
+
if (DEFAULT_STATUS_DEFINITIONS[statusKey]) {
|
|
36
|
+
return DEFAULT_STATUS_DEFINITIONS[statusKey];
|
|
37
|
+
}
|
|
38
|
+
// Fallback heuristic for unknown custom statuses:
|
|
39
|
+
const isDoneKey = ['done', 'completed', 'finished', 'closed', 'canceled', 'cancelled'].includes(statusKey.toLowerCase());
|
|
40
|
+
const isActiveKey = ['in_progress', 'active', 'working', 'in_review', 'review'].includes(statusKey.toLowerCase());
|
|
41
|
+
return {
|
|
42
|
+
key: statusKey,
|
|
43
|
+
label: statusKey,
|
|
44
|
+
completionState: isDoneKey ? 'done' : 'not_done',
|
|
45
|
+
executionState: isActiveKey ? 'active' : 'inactive',
|
|
46
|
+
isCancelled: statusKey.toLowerCase().includes('cancel')
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function deriveTaskLifecycleState(
|
|
51
|
+
task: Task,
|
|
52
|
+
customDefinitions?: StatusDefinition[],
|
|
53
|
+
referenceDate: Date = new Date()
|
|
54
|
+
): TaskLifecycleState {
|
|
55
|
+
const def = resolveStatusDefinition(task.status, customDefinitions);
|
|
56
|
+
const isDone = def.completionState === 'done';
|
|
57
|
+
const isActive = def.executionState === 'active';
|
|
58
|
+
const isCancelled = Boolean(def.isCancelled);
|
|
59
|
+
|
|
60
|
+
let isOverdue = false;
|
|
61
|
+
if (!isDone && task.dueDate) {
|
|
62
|
+
const due = new Date(task.dueDate);
|
|
63
|
+
if (!isNaN(due.getTime()) && due < referenceDate) {
|
|
64
|
+
isOverdue = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let isUpcoming = false;
|
|
69
|
+
if (!isDone && !isActive && task.plannedStartDate) {
|
|
70
|
+
const plannedStart = new Date(task.plannedStartDate);
|
|
71
|
+
if (!isNaN(plannedStart.getTime()) && plannedStart > referenceDate) {
|
|
72
|
+
isUpcoming = true;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
completionState: def.completionState,
|
|
78
|
+
executionState: def.executionState,
|
|
79
|
+
isDone,
|
|
80
|
+
isActive,
|
|
81
|
+
isCancelled,
|
|
82
|
+
isOverdue,
|
|
83
|
+
isUpcoming
|
|
84
|
+
};
|
|
85
|
+
}
|
package/tsconfig.tsbuildinfo
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/index.test.ts","./src/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts"],"version":"5.9.3"}
|
|
1
|
+
{"root":["./src/index.test.ts","./src/index.ts","./src/engine/index.ts","./src/plugins/index.ts","./src/store/firebase.test.ts","./src/store/firebase.ts","./src/store/index.ts","./src/store/sqlite.test.ts","./src/store/sqlite.ts","./src/types/index.ts","./src/utils/index.ts","./src/utils/key.test.ts","./src/utils/key.ts","./src/utils/status.ts"],"version":"5.9.3"}
|