prjct-cli 0.6.0 → 0.7.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 +67 -6
- package/CLAUDE.md +442 -36
- package/README.md +47 -54
- package/bin/prjct +170 -240
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +106 -62
- package/core/commands.js +2030 -2211
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +4 -12
- package/templates/agents/AGENTS.md +101 -27
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +9 -2
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +5 -2
- package/templates/commands/cleanup.md +5 -2
- package/templates/commands/design.md +5 -2
- package/templates/commands/done.md +4 -2
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +41 -10
- package/templates/commands/git.md +7 -2
- package/templates/commands/help.md +2 -2
- package/templates/commands/idea.md +14 -5
- package/templates/commands/init.md +62 -7
- package/templates/commands/next.md +4 -2
- package/templates/commands/now.md +4 -2
- package/templates/commands/progress.md +27 -5
- package/templates/commands/recap.md +39 -10
- package/templates/commands/roadmap.md +19 -5
- package/templates/commands/ship.md +118 -16
- package/templates/commands/status.md +4 -2
- package/templates/commands/sync.md +19 -15
- package/templates/commands/task.md +4 -2
- package/templates/commands/test.md +5 -2
- package/templates/commands/workflow.md +4 -2
- package/core/agent-generator.js +0 -525
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/ascii-graphics.js +0 -433
- package/core/git-integration.js +0 -401
- package/core/task-schema.js +0 -342
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -27
- package/templates/agents/coordinator.template.md +0 -34
- package/templates/agents/data.template.md +0 -27
- package/templates/agents/devops.template.md +0 -27
- package/templates/agents/fe.template.md +0 -27
- package/templates/agents/mobile.template.md +0 -27
- package/templates/agents/qa.template.md +0 -27
- package/templates/agents/scribe.template.md +0 -29
- package/templates/agents/security.template.md +0 -27
- package/templates/agents/ux.template.md +0 -27
- package/templates/commands/context.md +0 -36
- package/templates/commands/stuck.md +0 -36
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
const https = require('https')
|
|
2
|
-
const fs = require('fs')
|
|
3
|
-
const path = require('path')
|
|
4
|
-
const os = require('os')
|
|
1
|
+
const https = require('https')
|
|
2
|
+
const fs = require('fs')
|
|
3
|
+
const path = require('path')
|
|
4
|
+
const os = require('os')
|
|
5
5
|
|
|
6
6
|
class UpdateChecker {
|
|
7
7
|
constructor() {
|
|
8
|
-
this.packageName = 'prjct-cli'
|
|
9
|
-
this.cacheDir = path.join(os.homedir(), '.prjct-cli', 'config')
|
|
10
|
-
this.cacheFile = path.join(this.cacheDir, 'update-cache.json')
|
|
11
|
-
this.checkInterval = 24 * 60 * 60 * 1000
|
|
8
|
+
this.packageName = 'prjct-cli'
|
|
9
|
+
this.cacheDir = path.join(os.homedir(), '.prjct-cli', 'config')
|
|
10
|
+
this.cacheFile = path.join(this.cacheDir, 'update-cache.json')
|
|
11
|
+
this.checkInterval = 24 * 60 * 60 * 1000 // 24 hours in milliseconds
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -16,12 +16,12 @@ class UpdateChecker {
|
|
|
16
16
|
*/
|
|
17
17
|
getCurrentVersion() {
|
|
18
18
|
try {
|
|
19
|
-
const packageJsonPath = path.join(__dirname, '..', 'package.json')
|
|
20
|
-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
21
|
-
return packageJson.version
|
|
19
|
+
const packageJsonPath = path.join(__dirname, '..', '..', 'package.json')
|
|
20
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
|
|
21
|
+
return packageJson.version
|
|
22
22
|
} catch (error) {
|
|
23
|
-
console.error('Error reading package version:', error.message)
|
|
24
|
-
return null
|
|
23
|
+
console.error('Error reading package version:', error.message)
|
|
24
|
+
return null
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -36,42 +36,42 @@ class UpdateChecker {
|
|
|
36
36
|
method: 'GET',
|
|
37
37
|
headers: {
|
|
38
38
|
'User-Agent': 'prjct-cli-update-checker',
|
|
39
|
-
Accept: 'application/json'
|
|
40
|
-
}
|
|
41
|
-
}
|
|
39
|
+
Accept: 'application/json',
|
|
40
|
+
},
|
|
41
|
+
}
|
|
42
42
|
|
|
43
|
-
const req = https.request(options, res => {
|
|
44
|
-
let data = ''
|
|
43
|
+
const req = https.request(options, (res) => {
|
|
44
|
+
let data = ''
|
|
45
45
|
|
|
46
|
-
res.on('data', chunk => {
|
|
47
|
-
data += chunk
|
|
48
|
-
})
|
|
46
|
+
res.on('data', (chunk) => {
|
|
47
|
+
data += chunk
|
|
48
|
+
})
|
|
49
49
|
|
|
50
50
|
res.on('end', () => {
|
|
51
51
|
try {
|
|
52
52
|
if (res.statusCode === 200) {
|
|
53
|
-
const packageData = JSON.parse(data)
|
|
54
|
-
resolve(packageData.version)
|
|
53
|
+
const packageData = JSON.parse(data)
|
|
54
|
+
resolve(packageData.version)
|
|
55
55
|
} else {
|
|
56
|
-
reject(new Error(`npm registry returned status ${res.statusCode}`))
|
|
56
|
+
reject(new Error(`npm registry returned status ${res.statusCode}`))
|
|
57
57
|
}
|
|
58
58
|
} catch (error) {
|
|
59
|
-
reject(error)
|
|
59
|
+
reject(error)
|
|
60
60
|
}
|
|
61
|
-
})
|
|
62
|
-
})
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
63
|
|
|
64
|
-
req.on('error', error => {
|
|
65
|
-
reject(error)
|
|
66
|
-
})
|
|
64
|
+
req.on('error', (error) => {
|
|
65
|
+
reject(error)
|
|
66
|
+
})
|
|
67
67
|
|
|
68
68
|
req.setTimeout(5000, () => {
|
|
69
|
-
req.destroy()
|
|
70
|
-
reject(new Error('Request timeout'))
|
|
71
|
-
})
|
|
69
|
+
req.destroy()
|
|
70
|
+
reject(new Error('Request timeout'))
|
|
71
|
+
})
|
|
72
72
|
|
|
73
|
-
req.end()
|
|
74
|
-
})
|
|
73
|
+
req.end()
|
|
74
|
+
})
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/**
|
|
@@ -79,18 +79,18 @@ class UpdateChecker {
|
|
|
79
79
|
* Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal
|
|
80
80
|
*/
|
|
81
81
|
compareVersions(v1, v2) {
|
|
82
|
-
const parts1 = v1.split('.').map(Number)
|
|
83
|
-
const parts2 = v2.split('.').map(Number)
|
|
82
|
+
const parts1 = v1.split('.').map(Number)
|
|
83
|
+
const parts2 = v2.split('.').map(Number)
|
|
84
84
|
|
|
85
85
|
for (let i = 0; i < 3; i++) {
|
|
86
|
-
const part1 = parts1[i] || 0
|
|
87
|
-
const part2 = parts2[i] || 0
|
|
86
|
+
const part1 = parts1[i] || 0
|
|
87
|
+
const part2 = parts2[i] || 0
|
|
88
88
|
|
|
89
|
-
if (part1 > part2) return 1
|
|
90
|
-
if (part1 < part2) return -1
|
|
89
|
+
if (part1 > part2) return 1
|
|
90
|
+
if (part1 < part2) return -1
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
return 0
|
|
93
|
+
return 0
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
/**
|
|
@@ -99,13 +99,13 @@ class UpdateChecker {
|
|
|
99
99
|
readCache() {
|
|
100
100
|
try {
|
|
101
101
|
if (fs.existsSync(this.cacheFile)) {
|
|
102
|
-
const cache = JSON.parse(fs.readFileSync(this.cacheFile, 'utf8'))
|
|
103
|
-
return cache
|
|
102
|
+
const cache = JSON.parse(fs.readFileSync(this.cacheFile, 'utf8'))
|
|
103
|
+
return cache
|
|
104
104
|
}
|
|
105
105
|
} catch (error) {
|
|
106
106
|
// Cache file doesn't exist or is corrupted, ignore
|
|
107
107
|
}
|
|
108
|
-
return null
|
|
108
|
+
return null
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
/**
|
|
@@ -115,10 +115,10 @@ class UpdateChecker {
|
|
|
115
115
|
try {
|
|
116
116
|
// Ensure cache directory exists
|
|
117
117
|
if (!fs.existsSync(this.cacheDir)) {
|
|
118
|
-
fs.mkdirSync(this.cacheDir, { recursive: true })
|
|
118
|
+
fs.mkdirSync(this.cacheDir, { recursive: true })
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
-
fs.writeFileSync(this.cacheFile, JSON.stringify(data, null, 2), 'utf8')
|
|
121
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(data, null, 2), 'utf8')
|
|
122
122
|
} catch (error) {
|
|
123
123
|
// Fail silently - cache is not critical
|
|
124
124
|
}
|
|
@@ -130,14 +130,14 @@ class UpdateChecker {
|
|
|
130
130
|
*/
|
|
131
131
|
async checkForUpdates() {
|
|
132
132
|
try {
|
|
133
|
-
const currentVersion = this.getCurrentVersion()
|
|
133
|
+
const currentVersion = this.getCurrentVersion()
|
|
134
134
|
if (!currentVersion) {
|
|
135
|
-
return null
|
|
135
|
+
return null
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
// Check cache first
|
|
139
|
-
const cache = this.readCache()
|
|
140
|
-
const now = Date.now()
|
|
139
|
+
const cache = this.readCache()
|
|
140
|
+
const now = Date.now()
|
|
141
141
|
|
|
142
142
|
if (cache && cache.lastCheck && now - cache.lastCheck < this.checkInterval) {
|
|
143
143
|
// Cache is still valid
|
|
@@ -145,37 +145,37 @@ class UpdateChecker {
|
|
|
145
145
|
return {
|
|
146
146
|
updateAvailable: true,
|
|
147
147
|
currentVersion,
|
|
148
|
-
latestVersion: cache.latestVersion
|
|
149
|
-
}
|
|
148
|
+
latestVersion: cache.latestVersion,
|
|
149
|
+
}
|
|
150
150
|
}
|
|
151
151
|
return {
|
|
152
152
|
updateAvailable: false,
|
|
153
153
|
currentVersion,
|
|
154
|
-
latestVersion: currentVersion
|
|
155
|
-
}
|
|
154
|
+
latestVersion: currentVersion,
|
|
155
|
+
}
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// Cache expired or doesn't exist, fetch from npm
|
|
159
|
-
const latestVersion = await this.getLatestVersion()
|
|
159
|
+
const latestVersion = await this.getLatestVersion()
|
|
160
160
|
|
|
161
161
|
// Update cache
|
|
162
162
|
this.writeCache({
|
|
163
163
|
lastCheck: now,
|
|
164
|
-
latestVersion
|
|
165
|
-
})
|
|
164
|
+
latestVersion,
|
|
165
|
+
})
|
|
166
166
|
|
|
167
167
|
// Compare versions
|
|
168
|
-
const updateAvailable = this.compareVersions(latestVersion, currentVersion) > 0
|
|
168
|
+
const updateAvailable = this.compareVersions(latestVersion, currentVersion) > 0
|
|
169
169
|
|
|
170
170
|
return {
|
|
171
171
|
updateAvailable,
|
|
172
172
|
currentVersion,
|
|
173
|
-
latestVersion
|
|
174
|
-
}
|
|
173
|
+
latestVersion,
|
|
174
|
+
}
|
|
175
175
|
} catch (error) {
|
|
176
176
|
// Network error or other issue - fail silently
|
|
177
177
|
// Return null to indicate check couldn't be performed
|
|
178
|
-
return null
|
|
178
|
+
return null
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
@@ -183,13 +183,13 @@ class UpdateChecker {
|
|
|
183
183
|
* Get formatted update notification message
|
|
184
184
|
*/
|
|
185
185
|
async getUpdateNotification() {
|
|
186
|
-
const result = await this.checkForUpdates()
|
|
186
|
+
const result = await this.checkForUpdates()
|
|
187
187
|
|
|
188
188
|
if (!result || !result.updateAvailable) {
|
|
189
|
-
return null
|
|
189
|
+
return null
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
const chalk = require('chalk')
|
|
192
|
+
const chalk = require('chalk')
|
|
193
193
|
|
|
194
194
|
return (
|
|
195
195
|
'\n' +
|
|
@@ -215,8 +215,8 @@ class UpdateChecker {
|
|
|
215
215
|
'\n' +
|
|
216
216
|
chalk.yellow('└───────────────────────────────────────────────────────────┘') +
|
|
217
217
|
'\n'
|
|
218
|
-
)
|
|
218
|
+
)
|
|
219
219
|
}
|
|
220
220
|
}
|
|
221
221
|
|
|
222
|
-
module.exports = UpdateChecker
|
|
222
|
+
module.exports = UpdateChecker
|
|
@@ -38,18 +38,7 @@ const frames = {
|
|
|
38
38
|
'。゚☆: *.☽ .* :☆゚. ✨ ・ 。゚',
|
|
39
39
|
'☆: *.☽ .* :☆゚. ✨ ・ 。゚☆:',
|
|
40
40
|
],
|
|
41
|
-
loading: [
|
|
42
|
-
'⠋',
|
|
43
|
-
'⠙',
|
|
44
|
-
'⠹',
|
|
45
|
-
'⠸',
|
|
46
|
-
'⠼',
|
|
47
|
-
'⠴',
|
|
48
|
-
'⠦',
|
|
49
|
-
'⠧',
|
|
50
|
-
'⠇',
|
|
51
|
-
'⠏',
|
|
52
|
-
],
|
|
41
|
+
loading: ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'],
|
|
53
42
|
progress: [
|
|
54
43
|
'[ ]',
|
|
55
44
|
'[▓ ]',
|
|
@@ -63,16 +52,7 @@ const frames = {
|
|
|
63
52
|
'[▓▓▓▓▓▓▓▓▓ ]',
|
|
64
53
|
'[▓▓▓▓▓▓▓▓▓▓]',
|
|
65
54
|
],
|
|
66
|
-
celebration: [
|
|
67
|
-
'🎉',
|
|
68
|
-
'🎊',
|
|
69
|
-
'✨',
|
|
70
|
-
'🌟',
|
|
71
|
-
'⭐',
|
|
72
|
-
'💫',
|
|
73
|
-
'🎆',
|
|
74
|
-
'🎇',
|
|
75
|
-
],
|
|
55
|
+
celebration: ['🎉', '🎊', '✨', '🌟', '⭐', '💫', '🎆', '🎇'],
|
|
76
56
|
}
|
|
77
57
|
|
|
78
58
|
const banners = {
|
|
@@ -150,7 +130,7 @@ async function sparkle(message) {
|
|
|
150
130
|
}
|
|
151
131
|
|
|
152
132
|
function sleep(ms) {
|
|
153
|
-
return new Promise(resolve => setTimeout(resolve, ms))
|
|
133
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
154
134
|
}
|
|
155
135
|
|
|
156
136
|
function formatShip(feature, count) {
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Date Helper - Centralized date operations and formatting
|
|
3
|
+
*
|
|
4
|
+
* Eliminates duplicated date logic across:
|
|
5
|
+
* - session-manager.js (_getDateKey, _getTodayKey)
|
|
6
|
+
* - path-manager.js (getSessionPath date formatting)
|
|
7
|
+
* - commands.js (38+ inline date operations)
|
|
8
|
+
*
|
|
9
|
+
* @module date-helper
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Format a date to YYYY-MM-DD format
|
|
14
|
+
*
|
|
15
|
+
* @param {Date} date - Date to format
|
|
16
|
+
* @returns {string} - Formatted date string (e.g., "2025-10-04")
|
|
17
|
+
*/
|
|
18
|
+
function formatDate(date) {
|
|
19
|
+
const year = date.getFullYear()
|
|
20
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
21
|
+
const day = date.getDate().toString().padStart(2, '0')
|
|
22
|
+
return `${year}-${month}-${day}`
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Format a date to YYYY-MM format
|
|
27
|
+
*
|
|
28
|
+
* @param {Date} date - Date to format
|
|
29
|
+
* @returns {string} - Formatted month string (e.g., "2025-10")
|
|
30
|
+
*/
|
|
31
|
+
function formatMonth(date) {
|
|
32
|
+
const year = date.getFullYear()
|
|
33
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
|
34
|
+
return `${year}-${month}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get date key for today (YYYY-MM-DD)
|
|
39
|
+
*
|
|
40
|
+
* @returns {string} - Today's date key
|
|
41
|
+
*/
|
|
42
|
+
function getTodayKey() {
|
|
43
|
+
return formatDate(new Date())
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get date key for any date (YYYY-MM-DD)
|
|
48
|
+
* Alias for formatDate for consistency with session-manager
|
|
49
|
+
*
|
|
50
|
+
* @param {Date} date - Date to format
|
|
51
|
+
* @returns {string} - Date key (e.g., "2025-10-04")
|
|
52
|
+
*/
|
|
53
|
+
function getDateKey(date) {
|
|
54
|
+
return formatDate(date)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get year, month, day components from a date
|
|
59
|
+
* Useful for path construction
|
|
60
|
+
*
|
|
61
|
+
* @param {Date} date - Date to parse
|
|
62
|
+
* @returns {{year: string, month: string, day: string}} - Date components
|
|
63
|
+
*/
|
|
64
|
+
function getYearMonthDay(date) {
|
|
65
|
+
return {
|
|
66
|
+
year: date.getFullYear().toString(),
|
|
67
|
+
month: (date.getMonth() + 1).toString().padStart(2, '0'),
|
|
68
|
+
day: date.getDate().toString().padStart(2, '0'),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Parse a date string to Date object
|
|
74
|
+
* Supports: YYYY-MM-DD, YYYY-MM, ISO strings
|
|
75
|
+
*
|
|
76
|
+
* @param {string} dateString - Date string to parse
|
|
77
|
+
* @returns {Date} - Parsed date
|
|
78
|
+
*/
|
|
79
|
+
function parseDate(dateString) {
|
|
80
|
+
return new Date(dateString)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get current timestamp in ISO format
|
|
85
|
+
*
|
|
86
|
+
* @returns {string} - ISO timestamp (e.g., "2025-10-04T14:30:00.000Z")
|
|
87
|
+
*/
|
|
88
|
+
function getTimestamp() {
|
|
89
|
+
return new Date().toISOString()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get date N days ago from today
|
|
94
|
+
*
|
|
95
|
+
* @param {number} days - Number of days to subtract
|
|
96
|
+
* @returns {Date} - Date in the past
|
|
97
|
+
*/
|
|
98
|
+
function getDaysAgo(days) {
|
|
99
|
+
const date = new Date()
|
|
100
|
+
date.setDate(date.getDate() - days)
|
|
101
|
+
return date
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get date N days from today
|
|
106
|
+
*
|
|
107
|
+
* @param {number} days - Number of days to add
|
|
108
|
+
* @returns {Date} - Date in the future
|
|
109
|
+
*/
|
|
110
|
+
function getDaysFromNow(days) {
|
|
111
|
+
const date = new Date()
|
|
112
|
+
date.setDate(date.getDate() + days)
|
|
113
|
+
return date
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get date range between two dates
|
|
118
|
+
*
|
|
119
|
+
* @param {Date} fromDate - Start date
|
|
120
|
+
* @param {Date} toDate - End date
|
|
121
|
+
* @returns {Array<Date>} - Array of dates in range
|
|
122
|
+
*/
|
|
123
|
+
function getDateRange(fromDate, toDate) {
|
|
124
|
+
const dates = []
|
|
125
|
+
let current = new Date(fromDate)
|
|
126
|
+
|
|
127
|
+
while (current <= toDate) {
|
|
128
|
+
dates.push(new Date(current))
|
|
129
|
+
current = new Date(
|
|
130
|
+
current.getFullYear(),
|
|
131
|
+
current.getMonth(),
|
|
132
|
+
current.getDate() + 1,
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return dates
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a date is today
|
|
141
|
+
*
|
|
142
|
+
* @param {Date} date - Date to check
|
|
143
|
+
* @returns {boolean} - True if date is today
|
|
144
|
+
*/
|
|
145
|
+
function isToday(date) {
|
|
146
|
+
return formatDate(date) === getTodayKey()
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if a date is within the last N days
|
|
151
|
+
*
|
|
152
|
+
* @param {Date} date - Date to check
|
|
153
|
+
* @param {number} days - Number of days
|
|
154
|
+
* @returns {boolean} - True if within range
|
|
155
|
+
*/
|
|
156
|
+
function isWithinLastDays(date, days) {
|
|
157
|
+
const threshold = getDaysAgo(days)
|
|
158
|
+
return date >= threshold
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Format duration in human-readable format
|
|
163
|
+
*
|
|
164
|
+
* @param {number} milliseconds - Duration in ms
|
|
165
|
+
* @returns {string} - Formatted duration (e.g., "2h 15m")
|
|
166
|
+
*/
|
|
167
|
+
function formatDuration(milliseconds) {
|
|
168
|
+
const seconds = Math.floor(milliseconds / 1000)
|
|
169
|
+
const minutes = Math.floor(seconds / 60)
|
|
170
|
+
const hours = Math.floor(minutes / 60)
|
|
171
|
+
const days = Math.floor(hours / 24)
|
|
172
|
+
|
|
173
|
+
if (days > 0) {
|
|
174
|
+
return `${days}d ${hours % 24}h`
|
|
175
|
+
}
|
|
176
|
+
if (hours > 0) {
|
|
177
|
+
return `${hours}h ${minutes % 60}m`
|
|
178
|
+
}
|
|
179
|
+
if (minutes > 0) {
|
|
180
|
+
return `${minutes}m`
|
|
181
|
+
}
|
|
182
|
+
return `${seconds}s`
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Calculate duration between two dates
|
|
187
|
+
*
|
|
188
|
+
* @param {Date} startDate - Start date
|
|
189
|
+
* @param {Date} endDate - End date (defaults to now)
|
|
190
|
+
* @returns {string} - Formatted duration
|
|
191
|
+
*/
|
|
192
|
+
function calculateDuration(startDate, endDate = new Date()) {
|
|
193
|
+
const milliseconds = endDate - startDate
|
|
194
|
+
return formatDuration(milliseconds)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get start of day (00:00:00.000)
|
|
199
|
+
*
|
|
200
|
+
* @param {Date} date - Date to process
|
|
201
|
+
* @returns {Date} - Start of day
|
|
202
|
+
*/
|
|
203
|
+
function getStartOfDay(date) {
|
|
204
|
+
const result = new Date(date)
|
|
205
|
+
result.setHours(0, 0, 0, 0)
|
|
206
|
+
return result
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get end of day (23:59:59.999)
|
|
211
|
+
*
|
|
212
|
+
* @param {Date} date - Date to process
|
|
213
|
+
* @returns {Date} - End of day
|
|
214
|
+
*/
|
|
215
|
+
function getEndOfDay(date) {
|
|
216
|
+
const result = new Date(date)
|
|
217
|
+
result.setHours(23, 59, 59, 999)
|
|
218
|
+
return result
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = {
|
|
222
|
+
formatDate,
|
|
223
|
+
formatMonth,
|
|
224
|
+
getTodayKey,
|
|
225
|
+
getDateKey,
|
|
226
|
+
getYearMonthDay,
|
|
227
|
+
parseDate,
|
|
228
|
+
getTimestamp,
|
|
229
|
+
getDaysAgo,
|
|
230
|
+
getDaysFromNow,
|
|
231
|
+
getDateRange,
|
|
232
|
+
isToday,
|
|
233
|
+
isWithinLastDays,
|
|
234
|
+
formatDuration,
|
|
235
|
+
calculateDuration,
|
|
236
|
+
getStartOfDay,
|
|
237
|
+
getEndOfDay,
|
|
238
|
+
}
|