@andrestobelem/clone 0.1.3 → 0.2.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/LICENSE +21 -0
- package/README.md +151 -23
- package/app.js +645 -0
- package/bin/clone.js +11 -230
- package/dist/actors.js +49 -0
- package/dist/actors.js.map +1 -0
- package/dist/automations.js +57 -0
- package/dist/automations.js.map +1 -0
- package/dist/cli.js +565 -0
- package/dist/cli.js.map +1 -0
- package/dist/database.js +62 -0
- package/dist/database.js.map +1 -0
- package/dist/errors.js +46 -0
- package/dist/errors.js.map +1 -0
- package/dist/issues.js +264 -0
- package/dist/issues.js.map +1 -0
- package/dist/paths.js +42 -0
- package/dist/paths.js.map +1 -0
- package/dist/projects.js +134 -0
- package/dist/projects.js.map +1 -0
- package/dist/recurring.js +45 -0
- package/dist/recurring.js.map +1 -0
- package/dist/schema.js +52 -0
- package/dist/schema.js.map +1 -0
- package/dist/seed.js +498 -0
- package/dist/seed.js.map +1 -0
- package/dist/server.js +232 -0
- package/dist/server.js.map +1 -0
- package/dist/sync.js +625 -0
- package/dist/sync.js.map +1 -0
- package/dist/teams.js +77 -0
- package/dist/teams.js.map +1 -0
- package/dist/time.js +4 -0
- package/dist/time.js.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.js +254 -0
- package/dist/validation.js.map +1 -0
- package/dist/workspace-helpers.js +75 -0
- package/dist/workspace-helpers.js.map +1 -0
- package/dist/workspace-mutations.js +160 -0
- package/dist/workspace-mutations.js.map +1 -0
- package/dist/workspace-reads.js +62 -0
- package/dist/workspace-reads.js.map +1 -0
- package/dist/workspace.js +8 -0
- package/dist/workspace.js.map +1 -0
- package/index.html +73 -0
- package/package.json +33 -4
- package/styles.css +108 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export class CloneError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
exitCode;
|
|
4
|
+
details;
|
|
5
|
+
constructor(message, code, exitCode, details = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.code = code;
|
|
8
|
+
this.exitCode = exitCode;
|
|
9
|
+
this.details = details;
|
|
10
|
+
this.name = "CloneError";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export class InvalidInputError extends CloneError {
|
|
14
|
+
constructor(message) {
|
|
15
|
+
super(message, "invalid_input", 2);
|
|
16
|
+
this.name = "InvalidInputError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export class SyncConflictError extends CloneError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message, "sync_conflict", 3);
|
|
22
|
+
this.name = "SyncConflictError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class RelationshipConflictError extends CloneError {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message, "relationship_conflict", 3);
|
|
28
|
+
this.name = "RelationshipConflictError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class StorageError extends CloneError {
|
|
32
|
+
constructor(message) {
|
|
33
|
+
super(message, "storage_error", 4);
|
|
34
|
+
this.name = "StorageError";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export class PendingExportError extends CloneError {
|
|
38
|
+
constructor(message) {
|
|
39
|
+
super(message, "pending_export", 4, {
|
|
40
|
+
committed: true,
|
|
41
|
+
recoveryRequired: true,
|
|
42
|
+
});
|
|
43
|
+
this.name = "PendingExportError";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,UAAW,SAAQ,KAAK;IAGxB,IAAI;IACJ,QAAQ;IACR,OAAO;IAJlB,YACE,OAAe,EACN,IAAY,EACZ,QAAgB,EAChB,OAAO,GAA4B,EAAE;QAE9C,KAAK,CAAC,OAAO,CAAC,CAAC;oBAJN,IAAI;wBACJ,QAAQ;uBACR,OAAO;QAGhB,IAAI,CAAC,IAAI,GAAG,YAAY,CAAC;IAC3B,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,iBAAkB,SAAQ,UAAU;IAC/C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;IAClC,CAAC;CACF;AAED,MAAM,OAAO,yBAA0B,SAAQ,UAAU;IACvD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,uBAAuB,EAAE,CAAC,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,2BAA2B,CAAC;IAC1C,CAAC;CACF;AAED,MAAM,OAAO,YAAa,SAAQ,UAAU;IAC1C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;QACnC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,OAAO,kBAAmB,SAAQ,UAAU;IAChD,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,gBAAgB,EAAE,CAAC,EAAE;YAClC,SAAS,EAAE,IAAI;YACf,gBAAgB,EAAE,IAAI;SACvB,CAAC,CAAC;QACH,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;IACnC,CAAC;CACF"}
|
package/dist/issues.js
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { actorRecord } from "./actors.js";
|
|
5
|
+
import { lastInsertId, one, rows, run, scalar, sql, } from "./database.js";
|
|
6
|
+
import { priorities } from "./schema.js";
|
|
7
|
+
import { array, event, fail, isObject, parseJson, priorityNumber, replaceLinks, text, updateColumns, } from "./workspace-helpers.js";
|
|
8
|
+
export function mutateIssue(database, method, route, data, stamp, actor, paths, runAutomations) {
|
|
9
|
+
if (route === "/api/issues" && method === "POST") {
|
|
10
|
+
const title = text(data.title, "Issue title is required");
|
|
11
|
+
const teamId = Number(data.teamId ?? 1);
|
|
12
|
+
const team = one(database, "SELECT key FROM teams WHERE id=?", teamId);
|
|
13
|
+
if (!team)
|
|
14
|
+
fail("Team not found");
|
|
15
|
+
validateIssueLinks(database, data);
|
|
16
|
+
const number = nextIssueNumber(database, teamId, String(team.key));
|
|
17
|
+
const status = issueStatus(database, teamId, data.status);
|
|
18
|
+
run(database, "INSERT INTO issues(identifier,number,team_id,title,description,status_id,priority,assignee_id,creator_id,project_id,cycle_id,milestone_id,due_date,estimate,parent_id,recurring_rule,external_url,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", `${team.key}-${number}`, number, teamId, title, sql(data.description ?? ""), sql(status.id), priorityNumber(data.priority ?? 4), sql(data.assigneeId), actor, sql(data.projectId), sql(data.cycleId), sql(data.milestoneId), sql(data.dueDate), sql(data.estimate), sql(data.parentId), sql(data.recurringRule), sql(data.externalUrl), stamp, stamp);
|
|
19
|
+
const issueId = lastInsertId(database);
|
|
20
|
+
for (const label of array(data.labelIds))
|
|
21
|
+
run(database, "INSERT OR IGNORE INTO issue_labels(issue_id,label_id) VALUES(?,?)", issueId, sql(label));
|
|
22
|
+
run(database, "INSERT OR IGNORE INTO issue_subscribers(issue_id,member_id) VALUES(?,?)", issueId, actor);
|
|
23
|
+
for (const subscriber of array(data.subscriberIds))
|
|
24
|
+
run(database, "INSERT OR IGNORE INTO issue_subscribers VALUES(?,?)", issueId, sql(subscriber));
|
|
25
|
+
event(database, "issue", issueId, actor, "created", {
|
|
26
|
+
title,
|
|
27
|
+
status: String(status.name),
|
|
28
|
+
});
|
|
29
|
+
runAutomations(database, teamId, issueId, "issue.created", actor);
|
|
30
|
+
return { issue: issueRow(database, issueId), _status: 201 };
|
|
31
|
+
}
|
|
32
|
+
const base = route.match(/^\/api\/issues\/([^/]+)$/);
|
|
33
|
+
if (base && ["PUT", "PATCH"].includes(method)) {
|
|
34
|
+
const current = one(database, "SELECT * FROM issues WHERE identifier=?", base[1]);
|
|
35
|
+
if (!current)
|
|
36
|
+
fail("Issue not found");
|
|
37
|
+
const issueId = Number(current.id);
|
|
38
|
+
const targetTeam = Number(data.teamId ?? current.team_id);
|
|
39
|
+
validateIssueLinks(database, data, current);
|
|
40
|
+
const oldCategory = String(scalar(database, "SELECT category FROM issue_statuses WHERE id=?", sql(current.status_id)));
|
|
41
|
+
const changes = {};
|
|
42
|
+
const map = {
|
|
43
|
+
title: "title",
|
|
44
|
+
description: "description",
|
|
45
|
+
assigneeId: "assignee_id",
|
|
46
|
+
projectId: "project_id",
|
|
47
|
+
cycleId: "cycle_id",
|
|
48
|
+
milestoneId: "milestone_id",
|
|
49
|
+
dueDate: "due_date",
|
|
50
|
+
estimate: "estimate",
|
|
51
|
+
parentId: "parent_id",
|
|
52
|
+
recurringRule: "recurring_rule",
|
|
53
|
+
externalUrl: "external_url",
|
|
54
|
+
};
|
|
55
|
+
for (const [key, column] of Object.entries(map))
|
|
56
|
+
if (key in data)
|
|
57
|
+
changes[column] = data[key];
|
|
58
|
+
if ("priority" in data)
|
|
59
|
+
changes.priority = priorityNumber(data.priority);
|
|
60
|
+
if ("status" in data)
|
|
61
|
+
changes.status_id = issueStatus(database, targetTeam, String(data.status)).id;
|
|
62
|
+
if ("teamId" in data) {
|
|
63
|
+
if (!one(database, "SELECT id FROM teams WHERE id=?", targetTeam))
|
|
64
|
+
fail("Team not found");
|
|
65
|
+
changes.team_id = targetTeam;
|
|
66
|
+
if (!("status" in data)) {
|
|
67
|
+
const name = scalar(database, "SELECT name FROM issue_statuses WHERE id=?", sql(current.status_id));
|
|
68
|
+
changes.status_id = (one(database, "SELECT id FROM issue_statuses WHERE team_id=? AND name=?", targetTeam, sql(name)) ?? issueStatus(database, targetTeam)).id;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
updateColumns(database, "issues", "id", issueId, {
|
|
72
|
+
...changes,
|
|
73
|
+
updated_at: stamp,
|
|
74
|
+
});
|
|
75
|
+
if ("labelIds" in data)
|
|
76
|
+
replaceLinks(database, "issue_labels", "issue_id", issueId, "label_id", array(data.labelIds));
|
|
77
|
+
if ("subscriberIds" in data)
|
|
78
|
+
replaceLinks(database, "issue_subscribers", "issue_id", issueId, "member_id", array(data.subscriberIds));
|
|
79
|
+
const changed = Object.fromEntries(Object.keys(data)
|
|
80
|
+
.filter((key) => key in map ||
|
|
81
|
+
[
|
|
82
|
+
"priority",
|
|
83
|
+
"status",
|
|
84
|
+
"teamId",
|
|
85
|
+
"labelIds",
|
|
86
|
+
"subscriberIds",
|
|
87
|
+
].includes(key))
|
|
88
|
+
.map((key) => [key, data[key]]));
|
|
89
|
+
event(database, "issue", issueId, actor, "updated", changed);
|
|
90
|
+
runAutomations(database, targetTeam, issueId, "issue.updated", actor);
|
|
91
|
+
const newCategory = String(scalar(database, "SELECT s.category FROM issues i JOIN issue_statuses s ON s.id=i.status_id WHERE i.id=?", issueId));
|
|
92
|
+
if (oldCategory !== "Completed" && newCategory === "Completed")
|
|
93
|
+
runAutomations(database, targetTeam, issueId, "issue.completed", actor);
|
|
94
|
+
return { issue: issueRow(database, issueId) };
|
|
95
|
+
}
|
|
96
|
+
if (base && method === "DELETE") {
|
|
97
|
+
const row = one(database, "SELECT id FROM issues WHERE identifier=?", base[1]);
|
|
98
|
+
if (!row)
|
|
99
|
+
fail("Issue not found");
|
|
100
|
+
run(database, "UPDATE issues SET archived=1,updated_at=? WHERE id=?", stamp, sql(row.id));
|
|
101
|
+
event(database, "issue", Number(row.id), actor, "archived", {});
|
|
102
|
+
return { ok: true };
|
|
103
|
+
}
|
|
104
|
+
const action = route.match(/^\/api\/issues\/([^/]+)\/(restore|comments|attachments|relations)$/);
|
|
105
|
+
if (!action)
|
|
106
|
+
return null;
|
|
107
|
+
const issue = one(database, "SELECT id FROM issues WHERE identifier=?", action[1]);
|
|
108
|
+
if (!issue)
|
|
109
|
+
fail("Issue not found");
|
|
110
|
+
const issueId = Number(issue.id);
|
|
111
|
+
if (action[2] === "restore" && method === "POST") {
|
|
112
|
+
run(database, "UPDATE issues SET archived=0,updated_at=? WHERE id=?", stamp, issueId);
|
|
113
|
+
event(database, "issue", issueId, actor, "restored", {});
|
|
114
|
+
return { ok: true };
|
|
115
|
+
}
|
|
116
|
+
if (action[2] === "comments" && method === "POST") {
|
|
117
|
+
const body = text(data.body, "Comment is required");
|
|
118
|
+
run(database, "INSERT INTO comments(issue_id,author_id,body,created_at) VALUES(?,?,?,?)", issueId, actor, body, stamp);
|
|
119
|
+
event(database, "issue", issueId, actor, "commented", {
|
|
120
|
+
body: body.slice(0, 120),
|
|
121
|
+
});
|
|
122
|
+
return { issue: issueRow(database, issueId), _status: 201 };
|
|
123
|
+
}
|
|
124
|
+
if (action[2] === "relations" && method === "POST") {
|
|
125
|
+
const relatedIdentifier = data.relatedIssueId;
|
|
126
|
+
const url = data.url;
|
|
127
|
+
if (!relatedIdentifier && !url)
|
|
128
|
+
fail("Choose an issue or provide a URL");
|
|
129
|
+
let related = null;
|
|
130
|
+
if (relatedIdentifier) {
|
|
131
|
+
const found = one(database, "SELECT id FROM issues WHERE identifier=?", sql(relatedIdentifier));
|
|
132
|
+
if (!found)
|
|
133
|
+
fail("Related issue not found");
|
|
134
|
+
related = Number(found.id);
|
|
135
|
+
}
|
|
136
|
+
run(database, "INSERT INTO issue_relations(issue_id,related_issue_id,relation_type,url) VALUES(?,?,?,?)", issueId, related, sql(data.type ?? "relates to"), sql(url));
|
|
137
|
+
event(database, "issue", issueId, actor, "linked", {
|
|
138
|
+
relation: (relatedIdentifier ?? url),
|
|
139
|
+
});
|
|
140
|
+
return { issue: issueRow(database, issueId), _status: 201 };
|
|
141
|
+
}
|
|
142
|
+
if (action[2] === "attachments" && method === "POST")
|
|
143
|
+
return attachFiles(database, issueId, data, stamp, actor, paths);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
function attachFiles(database, issueId, data, stamp, actor, paths) {
|
|
147
|
+
const files = array(data.files);
|
|
148
|
+
if (!files.length)
|
|
149
|
+
fail("Choose at least one attachment");
|
|
150
|
+
fs.mkdirSync(paths.attachmentsDirectory, { recursive: true });
|
|
151
|
+
let total = 0;
|
|
152
|
+
const created = [];
|
|
153
|
+
for (const raw of files) {
|
|
154
|
+
const item = raw;
|
|
155
|
+
const name = path
|
|
156
|
+
.basename(String(item.name ?? "attachment"))
|
|
157
|
+
.trim()
|
|
158
|
+
.slice(0, 180) || "attachment";
|
|
159
|
+
let content;
|
|
160
|
+
try {
|
|
161
|
+
content = Buffer.from(String(item.data ?? "")
|
|
162
|
+
.split(",")
|
|
163
|
+
.at(-1) ?? "", "base64");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
fail("Attachment data is invalid");
|
|
167
|
+
}
|
|
168
|
+
total += content.length;
|
|
169
|
+
if (!content.length)
|
|
170
|
+
fail("Empty files cannot be attached");
|
|
171
|
+
if (content.length > 3_000_000 || total > 3_000_000)
|
|
172
|
+
fail("Attachments must total 3 MB or less");
|
|
173
|
+
const storageName = randomUUID().replaceAll("-", "");
|
|
174
|
+
fs.writeFileSync(path.join(paths.attachmentsDirectory, storageName), content);
|
|
175
|
+
const contentType = /^[A-Za-z0-9.+-]+\/[A-Za-z0-9.+-]+$/.test(String(item.type ?? ""))
|
|
176
|
+
? String(item.type)
|
|
177
|
+
: "application/octet-stream";
|
|
178
|
+
run(database, "INSERT INTO issue_attachments(issue_id,name,content_type,size,storage_name,created_at) VALUES(?,?,?,?,?,?)", issueId, name, contentType, content.length, storageName, stamp);
|
|
179
|
+
created.push({ id: lastInsertId(database), name });
|
|
180
|
+
}
|
|
181
|
+
event(database, "issue", issueId, actor, "attachments_added", {
|
|
182
|
+
count: created.length,
|
|
183
|
+
});
|
|
184
|
+
return { attachments: created, _status: 201 };
|
|
185
|
+
}
|
|
186
|
+
function validateIssueLinks(database, data, current = {}) {
|
|
187
|
+
const teamId = Number(data.teamId ?? current.team_id ?? 1);
|
|
188
|
+
const projectId = data.projectId ?? current.project_id;
|
|
189
|
+
if (projectId != null &&
|
|
190
|
+
!one(database, "SELECT 1 FROM project_teams WHERE project_id=? AND team_id=?", sql(projectId), teamId))
|
|
191
|
+
fail("Add this team to the project before linking its issues");
|
|
192
|
+
const cycle = data.cycleId ?? current.cycle_id;
|
|
193
|
+
if (cycle != null) {
|
|
194
|
+
const row = one(database, "SELECT team_id FROM cycles WHERE id=?", sql(cycle));
|
|
195
|
+
if (!row || Number(row.team_id) !== teamId)
|
|
196
|
+
fail("Issue cycle must belong to its team");
|
|
197
|
+
}
|
|
198
|
+
const milestone = data.milestoneId ?? current.milestone_id;
|
|
199
|
+
if (milestone != null) {
|
|
200
|
+
const row = one(database, "SELECT project_id FROM milestones WHERE id=?", sql(milestone));
|
|
201
|
+
if (!row || row.project_id !== projectId)
|
|
202
|
+
fail("Issue milestone must belong to its project");
|
|
203
|
+
}
|
|
204
|
+
let parent = data.parentId ?? current.parent_id;
|
|
205
|
+
const seen = new Set(current.id ? [Number(current.id)] : []);
|
|
206
|
+
while (parent != null) {
|
|
207
|
+
if (seen.has(Number(parent)))
|
|
208
|
+
fail("Issue parent links must not form a cycle");
|
|
209
|
+
seen.add(Number(parent));
|
|
210
|
+
const row = one(database, "SELECT parent_id FROM issues WHERE id=?", sql(parent));
|
|
211
|
+
if (!row)
|
|
212
|
+
fail("Parent issue not found");
|
|
213
|
+
parent = row.parent_id;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
function nextIssueNumber(database, teamId, key) {
|
|
217
|
+
return Number(scalar(database, "SELECT COALESCE(MAX(number),0)+1 FROM issues WHERE team_id=? OR substr(identifier,1,?)=?", teamId, key.length + 1, `${key}-`));
|
|
218
|
+
}
|
|
219
|
+
function issueStatus(database, teamId, name) {
|
|
220
|
+
const row = name === undefined
|
|
221
|
+
? one(database, "SELECT id,name FROM issue_statuses WHERE team_id=? AND is_default=1 ORDER BY position,id LIMIT 1", teamId)
|
|
222
|
+
: one(database, "SELECT id,name FROM issue_statuses WHERE team_id=? AND name=? ORDER BY position,id LIMIT 1", teamId, name);
|
|
223
|
+
if (!row)
|
|
224
|
+
fail(name === undefined
|
|
225
|
+
? "Team has no default workflow status"
|
|
226
|
+
: "Unknown workflow status");
|
|
227
|
+
return row;
|
|
228
|
+
}
|
|
229
|
+
export function issueRow(database, issueId) {
|
|
230
|
+
const result = one(database, `SELECT i.*,t.name AS team_name,t.key AS team_key,s.name AS status,s.category AS status_category,s.color AS status_color,
|
|
231
|
+
CASE WHEN a.kind='agent' THEN a.name||' (Agent)' ELSE a.name END AS assignee,(SELECT email FROM members WHERE id=a.member_id) AS assignee_email,a.avatar_color AS assignee_color,a.kind AS assignee_kind,c.kind AS creator_kind,CASE WHEN c.kind='agent' THEN c.name||' (Agent)' ELSE c.name END AS creator,
|
|
232
|
+
p.name AS project,cy.name AS cycle,m.name AS milestone
|
|
233
|
+
FROM issues i JOIN teams t ON t.id=i.team_id JOIN issue_statuses s ON s.id=i.status_id
|
|
234
|
+
LEFT JOIN actors a ON a.id=i.assignee_id LEFT JOIN actors c ON c.id=i.creator_id
|
|
235
|
+
LEFT JOIN projects p ON p.id=i.project_id LEFT JOIN cycles cy ON cy.id=i.cycle_id LEFT JOIN milestones m ON m.id=i.milestone_id
|
|
236
|
+
WHERE i.id=?`, issueId);
|
|
237
|
+
if (!result)
|
|
238
|
+
return null;
|
|
239
|
+
const output = {
|
|
240
|
+
...result,
|
|
241
|
+
priority_name: priorities[Number(result.priority)],
|
|
242
|
+
};
|
|
243
|
+
output.labels = rows(database, "SELECT l.id,l.name,l.color FROM labels l JOIN issue_labels il ON il.label_id=l.id WHERE il.issue_id=?", issueId);
|
|
244
|
+
output.subscribers = rows(database, "SELECT member_id FROM issue_subscribers WHERE issue_id=?", issueId).map((row) => actorRecord(database, Number(row.member_id)));
|
|
245
|
+
output.comments = rows(database, "SELECT c.id,c.body,c.created_at,m.id AS author_id,CASE WHEN m.kind='agent' THEN m.name||' (Agent)' ELSE m.name END AS author,m.avatar_color FROM comments c JOIN actors m ON m.id=c.author_id WHERE c.issue_id=? ORDER BY c.id", issueId);
|
|
246
|
+
output.attachments = rows(database, "SELECT id,name,content_type,size,created_at FROM issue_attachments WHERE issue_id=? ORDER BY id", issueId);
|
|
247
|
+
output.relations = rows(database, "SELECT r.id,r.relation_type,r.url,i.identifier AS related_identifier,i.title AS related_title FROM issue_relations r LEFT JOIN issues i ON i.id=r.related_issue_id WHERE r.issue_id=?", issueId);
|
|
248
|
+
output.subissues = rows(database, "SELECT id,identifier,title,status_id FROM issues WHERE parent_id=? AND archived=0", issueId);
|
|
249
|
+
output.status_changed_at = result.created_at;
|
|
250
|
+
let statusFound = false;
|
|
251
|
+
output.activity = rows(database, "SELECT e.*,CASE WHEN m.kind='agent' THEN m.name||' (Agent)' ELSE m.name END AS actor FROM activity_events e LEFT JOIN actors m ON m.id=e.actor_id WHERE e.entity_type='issue' AND e.entity_id=? ORDER BY e.created_at DESC,e.id DESC", issueId).map((entry) => {
|
|
252
|
+
const payload = parseJson(entry.payload, {});
|
|
253
|
+
if (!statusFound &&
|
|
254
|
+
("status" in payload ||
|
|
255
|
+
(Array.isArray(payload.changes) &&
|
|
256
|
+
payload.changes.some((change) => isObject(change) && change.field === "status_id")))) {
|
|
257
|
+
output.status_changed_at = entry.created_at;
|
|
258
|
+
statusFound = true;
|
|
259
|
+
}
|
|
260
|
+
return { ...entry, payload };
|
|
261
|
+
});
|
|
262
|
+
return output;
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=issues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"issues.js","sourceRoot":"","sources":["../src/issues.ts"],"names":[],"mappings":"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"}
|
package/dist/paths.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { InvalidInputError } from "./errors.js";
|
|
5
|
+
export const rootDirectory = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
6
|
+
export function defaultDataDirectory(platform = process.platform, environment = process.env) {
|
|
7
|
+
if (environment.CLONE_DATA_DIR) {
|
|
8
|
+
return path.resolve(environment.CLONE_DATA_DIR);
|
|
9
|
+
}
|
|
10
|
+
if (platform === "darwin") {
|
|
11
|
+
return path.join(os.homedir(), "Library", "Application Support", "Clone");
|
|
12
|
+
}
|
|
13
|
+
if (platform === "win32") {
|
|
14
|
+
return path.join(environment.APPDATA ?? os.homedir(), "Clone");
|
|
15
|
+
}
|
|
16
|
+
return path.join(environment.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share"), "clone");
|
|
17
|
+
}
|
|
18
|
+
export function storagePaths(database, snapshotDirectory) {
|
|
19
|
+
const resolvedDatabase = path.resolve(database);
|
|
20
|
+
const dataDirectory = path.dirname(resolvedDatabase);
|
|
21
|
+
const resolvedSnapshot = path.resolve(snapshotDirectory ?? path.join(dataDirectory, "snapshot"));
|
|
22
|
+
const attachmentsDirectory = path.join(dataDirectory, "attachments");
|
|
23
|
+
const backupsDirectory = path.join(dataDirectory, "backups");
|
|
24
|
+
if (containsPath(resolvedSnapshot, dataDirectory) ||
|
|
25
|
+
containsPath(attachmentsDirectory, resolvedSnapshot) ||
|
|
26
|
+
containsPath(backupsDirectory, resolvedSnapshot)) {
|
|
27
|
+
throw new InvalidInputError("Snapshot directory must be separate from database storage, attachments, and backups");
|
|
28
|
+
}
|
|
29
|
+
return {
|
|
30
|
+
database: resolvedDatabase,
|
|
31
|
+
dataDirectory,
|
|
32
|
+
snapshotDirectory: resolvedSnapshot,
|
|
33
|
+
attachmentsDirectory,
|
|
34
|
+
backupsDirectory,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function containsPath(parent, candidate) {
|
|
38
|
+
const relative = path.relative(parent, candidate);
|
|
39
|
+
return (relative === "" ||
|
|
40
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative)));
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../src/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CACvC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAC5C,IAAI,CACL,CAAC;AAEF,MAAM,UAAU,oBAAoB,CAClC,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAC3B,WAAW,GAAG,OAAO,CAAC,GAAG;IAEzB,IAAI,WAAW,CAAC,cAAc,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,cAAc,CAAC,CAAC;IAClD,CAAC;IACD,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,qBAAqB,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;IACD,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,IAAI,EAAE,CAAC,OAAO,EAAE,EAAE,OAAO,CAAC,CAAC;IACjE,CAAC;IACD,OAAO,IAAI,CAAC,IAAI,CACd,WAAW,CAAC,aAAa,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,EACvE,OAAO,CACR,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAC1B,QAAgB,EAChB,iBAA0B;IAE1B,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAChD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACrD,MAAM,gBAAgB,GAAG,IAAI,CAAC,OAAO,CACnC,iBAAiB,IAAI,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,UAAU,CAAC,CAC1D,CAAC;IACF,MAAM,oBAAoB,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,aAAa,CAAC,CAAC;IACrE,MAAM,gBAAgB,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IAE7D,IACE,YAAY,CAAC,gBAAgB,EAAE,aAAa,CAAC;QAC7C,YAAY,CAAC,oBAAoB,EAAE,gBAAgB,CAAC;QACpD,YAAY,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,EAChD,CAAC;QACD,MAAM,IAAI,iBAAiB,CACzB,qFAAqF,CACtF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,gBAAgB;QAC1B,aAAa;QACb,iBAAiB,EAAE,gBAAgB;QACnC,oBAAoB;QACpB,gBAAgB;KACjB,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,MAAc,EAAE,SAAiB;IACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IAClD,OAAO,CACL,QAAQ,KAAK,EAAE;QACf,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAC3D,CAAC;AACJ,CAAC"}
|
package/dist/projects.js
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { actorRecord } from "./actors.js";
|
|
2
|
+
import { lastInsertId, one, rows, run, scalar, sql, } from "./database.js";
|
|
3
|
+
import { priorities } from "./schema.js";
|
|
4
|
+
import { array, event, fail, priorityNumber, replaceLinks, text, updateFields, } from "./workspace-helpers.js";
|
|
5
|
+
export function mutateProject(database, method, route, data, stamp, actor) {
|
|
6
|
+
if (route === "/api/projects" && method === "POST") {
|
|
7
|
+
const name = text(data.name, "Project name is required");
|
|
8
|
+
run(database, "INSERT INTO projects(name,summary,description,status,priority,icon,lead_id,start_date,target_date,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?)", name, sql(data.summary ?? ""), sql(data.description ?? ""), sql(data.status ?? "Backlog"), priorityNumber(data.priority ?? 4), sql(data.icon ?? "◈"), sql(data.leadId ?? actor), sql(data.startDate), sql(data.targetDate), stamp, stamp);
|
|
9
|
+
const projectId = lastInsertId(database);
|
|
10
|
+
for (const teamId of array(data.teamIds, [1]))
|
|
11
|
+
run(database, "INSERT OR IGNORE INTO project_teams(project_id,team_id) VALUES(?,?)", projectId, sql(teamId));
|
|
12
|
+
for (const memberId of array(data.memberIds, [actor]))
|
|
13
|
+
run(database, "INSERT OR IGNORE INTO project_members(project_id,member_id) VALUES(?,?)", projectId, sql(memberId));
|
|
14
|
+
for (const labelId of array(data.labelIds))
|
|
15
|
+
run(database, "INSERT OR IGNORE INTO project_labels(project_id,label_id) VALUES(?,?)", projectId, sql(labelId));
|
|
16
|
+
for (const dependency of array(data.dependencies))
|
|
17
|
+
if (Number(dependency) !== projectId)
|
|
18
|
+
run(database, "INSERT OR IGNORE INTO project_dependencies(project_id,depends_on_id,dependency_type) VALUES(?,?,'blocks')", projectId, sql(dependency));
|
|
19
|
+
array(data.milestones).forEach((raw, position) => {
|
|
20
|
+
const item = raw;
|
|
21
|
+
run(database, "INSERT INTO milestones(project_id,name,due_date,status,position) VALUES(?,?,?,?,?)", projectId, sql(item.name), sql(item.dueDate), sql(item.status ?? "Todo"), position);
|
|
22
|
+
});
|
|
23
|
+
event(database, "project", projectId, actor, "created", { name });
|
|
24
|
+
return { projectId, _status: 201 };
|
|
25
|
+
}
|
|
26
|
+
const match = route.match(/^\/api\/projects\/(\d+)(?:\/(restore|milestones|updates|dependencies))?$/);
|
|
27
|
+
if (!match)
|
|
28
|
+
return null;
|
|
29
|
+
const projectId = Number(match[1]);
|
|
30
|
+
const action = match[2];
|
|
31
|
+
if (!one(database, "SELECT id FROM projects WHERE id=?", projectId))
|
|
32
|
+
fail("Project not found");
|
|
33
|
+
if (!action && ["PUT", "PATCH"].includes(method)) {
|
|
34
|
+
if ("teamIds" in data) {
|
|
35
|
+
const next = new Set(array(data.teamIds).map(Number));
|
|
36
|
+
if (rows(database, "SELECT DISTINCT team_id FROM issues WHERE project_id=?", projectId).some((row) => !next.has(Number(row.team_id))))
|
|
37
|
+
fail("Move or unlink this project's issues before removing their team");
|
|
38
|
+
}
|
|
39
|
+
const changed = updateFields(database, "projects", "id", projectId, data, {
|
|
40
|
+
name: "name",
|
|
41
|
+
summary: "summary",
|
|
42
|
+
description: "description",
|
|
43
|
+
status: "status",
|
|
44
|
+
icon: "icon",
|
|
45
|
+
leadId: "lead_id",
|
|
46
|
+
startDate: "start_date",
|
|
47
|
+
targetDate: "target_date",
|
|
48
|
+
}, { priority: priorityNumber });
|
|
49
|
+
if (changed)
|
|
50
|
+
run(database, "UPDATE projects SET updated_at=? WHERE id=?", stamp, projectId);
|
|
51
|
+
for (const [key, table, column] of [
|
|
52
|
+
["teamIds", "project_teams", "team_id"],
|
|
53
|
+
["memberIds", "project_members", "member_id"],
|
|
54
|
+
["labelIds", "project_labels", "label_id"],
|
|
55
|
+
])
|
|
56
|
+
if (key in data)
|
|
57
|
+
replaceLinks(database, table, "project_id", projectId, column, array(data[key]));
|
|
58
|
+
event(database, "project", projectId, actor, "updated", data);
|
|
59
|
+
return { projectId };
|
|
60
|
+
}
|
|
61
|
+
if (!action && method === "DELETE") {
|
|
62
|
+
run(database, "UPDATE projects SET archived=1,updated_at=? WHERE id=?", stamp, projectId);
|
|
63
|
+
event(database, "project", projectId, actor, "archived", {});
|
|
64
|
+
return { ok: true };
|
|
65
|
+
}
|
|
66
|
+
if (action === "restore" && method === "POST") {
|
|
67
|
+
run(database, "UPDATE projects SET archived=0,updated_at=? WHERE id=?", stamp, projectId);
|
|
68
|
+
event(database, "project", projectId, actor, "restored", {});
|
|
69
|
+
return { ok: true };
|
|
70
|
+
}
|
|
71
|
+
if (action === "milestones" && method === "POST") {
|
|
72
|
+
const name = text(data.name, "Milestone name is required");
|
|
73
|
+
const position = Number(scalar(database, "SELECT COUNT(*) FROM milestones WHERE project_id=?", projectId));
|
|
74
|
+
run(database, "INSERT INTO milestones(project_id,name,due_date,status,position) VALUES(?,?,?,?,?)", projectId, name, sql(data.dueDate), sql(data.status ?? "Todo"), position);
|
|
75
|
+
event(database, "project", projectId, actor, "milestone_created", { name });
|
|
76
|
+
return { ok: true, _status: 201 };
|
|
77
|
+
}
|
|
78
|
+
if (action === "updates" && method === "POST") {
|
|
79
|
+
const body = text(data.body, "Write an update before saving");
|
|
80
|
+
const health = String(data.health ?? "On track");
|
|
81
|
+
if (!["On track", "At risk", "Off track"].includes(health))
|
|
82
|
+
fail("Unknown project health");
|
|
83
|
+
run(database, "INSERT INTO project_updates(project_id,health,body,author_id,created_at) VALUES(?,?,?,?,?)", projectId, health, body, actor, stamp);
|
|
84
|
+
event(database, "project", projectId, actor, "update_posted", { health });
|
|
85
|
+
return { ok: true, _status: 201 };
|
|
86
|
+
}
|
|
87
|
+
if (action === "dependencies" && method === "POST") {
|
|
88
|
+
const dependency = Number(data.dependsOnId);
|
|
89
|
+
if (projectId === dependency)
|
|
90
|
+
fail("A project cannot depend on itself");
|
|
91
|
+
run(database, "INSERT OR IGNORE INTO project_dependencies(project_id,depends_on_id,dependency_type) VALUES(?,?,?)", projectId, dependency, sql(data.type ?? "blocks"));
|
|
92
|
+
event(database, "project", projectId, actor, "dependency_added", {
|
|
93
|
+
dependsOnId: dependency,
|
|
94
|
+
});
|
|
95
|
+
return { ok: true, _status: 201 };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
export function projectRecord(database, project) {
|
|
100
|
+
const id = Number(project.id);
|
|
101
|
+
const statuses = rows(database, "SELECT status_id,COUNT(*) AS n FROM issues WHERE project_id=? AND archived=0 GROUP BY status_id", id);
|
|
102
|
+
const done = new Set(rows(database, "SELECT id FROM issue_statuses WHERE category='Completed'").map((r) => Number(r.id)));
|
|
103
|
+
const total = statuses.reduce((n, r) => n + Number(r.n), 0);
|
|
104
|
+
const complete = statuses
|
|
105
|
+
.filter((r) => done.has(Number(r.status_id)))
|
|
106
|
+
.reduce((n, r) => n + Number(r.n), 0);
|
|
107
|
+
const milestoneTotal = Number(scalar(database, "SELECT COUNT(*) FROM milestones WHERE project_id=?", id));
|
|
108
|
+
const milestoneDone = Number(scalar(database, "SELECT COUNT(*) FROM milestones WHERE project_id=? AND status='Done'", id));
|
|
109
|
+
const parts = [
|
|
110
|
+
total ? complete / total : null,
|
|
111
|
+
milestoneTotal ? milestoneDone / milestoneTotal : null,
|
|
112
|
+
].filter((v) => v !== null);
|
|
113
|
+
return {
|
|
114
|
+
...project,
|
|
115
|
+
priority_name: priorities[Number(project.priority)],
|
|
116
|
+
lead: project.lead_id
|
|
117
|
+
? actorRecord(database, Number(project.lead_id))
|
|
118
|
+
: null,
|
|
119
|
+
teams: rows(database, "SELECT t.id,t.name,t.key,t.icon FROM teams t JOIN project_teams pt ON pt.team_id=t.id WHERE pt.project_id=?", id),
|
|
120
|
+
members: rows(database, "SELECT member_id FROM project_members WHERE project_id=?", id).map((r) => actorRecord(database, Number(r.member_id))),
|
|
121
|
+
labels: rows(database, "SELECT l.id,l.name,l.color FROM labels l JOIN project_labels pl ON pl.label_id=l.id WHERE pl.project_id=?", id),
|
|
122
|
+
milestones: rows(database, "SELECT * FROM milestones WHERE project_id=? ORDER BY position,id", id),
|
|
123
|
+
updates: rows(database, "SELECT u.*,CASE WHEN m.kind='agent' THEN m.name||' (Agent)' ELSE m.name END AS author FROM project_updates u LEFT JOIN actors m ON m.id=u.author_id WHERE u.project_id=? ORDER BY u.created_at DESC,u.id DESC", id),
|
|
124
|
+
dependencies: rows(database, "SELECT d.dependency_type,p.id,p.name FROM project_dependencies d JOIN projects p ON p.id=d.depends_on_id WHERE d.project_id=?", id),
|
|
125
|
+
issue_count: total,
|
|
126
|
+
completed_count: complete,
|
|
127
|
+
milestone_count: milestoneTotal,
|
|
128
|
+
completed_milestones: milestoneDone,
|
|
129
|
+
progress: Math.round((parts.reduce((a, b) => a + b, 0) / (parts.length || 1)) * 100),
|
|
130
|
+
issues: rows(database, "SELECT i.id,i.identifier,i.title,s.name AS status,i.priority FROM issues i JOIN issue_statuses s ON s.id=i.status_id WHERE i.project_id=? AND i.archived=0 ORDER BY i.updated_at DESC", id),
|
|
131
|
+
activity: rows(database, "SELECT e.actor_id,e.action,e.payload,e.created_at,CASE WHEN m.kind='agent' THEN m.name||' (Agent)' ELSE m.name END AS actor FROM activity_events e LEFT JOIN actors m ON m.id=e.actor_id WHERE e.entity_type='project' AND e.entity_id=? ORDER BY e.created_at DESC", id),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=projects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"projects.js","sourceRoot":"","sources":["../src/projects.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { rows, run, sql } from "./database.js";
|
|
2
|
+
import { dispatchMutation } from "./workspace-mutations.js";
|
|
3
|
+
export function runDueRecurring(database, paths) {
|
|
4
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
5
|
+
let created = 0;
|
|
6
|
+
for (const rule of rows(database, "SELECT * FROM recurring_rules WHERE enabled=1 ORDER BY id")) {
|
|
7
|
+
let due = String(rule.next_run).slice(0, 10);
|
|
8
|
+
if (due > today)
|
|
9
|
+
continue;
|
|
10
|
+
dispatchMutation(database, "POST", "/api/issues", {
|
|
11
|
+
actorId: 1,
|
|
12
|
+
teamId: Number(rule.team_id),
|
|
13
|
+
title: String(rule.title),
|
|
14
|
+
description: String(rule.description),
|
|
15
|
+
recurringRule: {
|
|
16
|
+
ruleId: Number(rule.id),
|
|
17
|
+
cadence: String(rule.cadence),
|
|
18
|
+
},
|
|
19
|
+
}, paths);
|
|
20
|
+
do
|
|
21
|
+
due = recurringNext(due, String(rule.cadence));
|
|
22
|
+
while (due <= today);
|
|
23
|
+
run(database, "UPDATE recurring_rules SET next_run=? WHERE id=?", due, sql(rule.id));
|
|
24
|
+
created += 1;
|
|
25
|
+
}
|
|
26
|
+
return created;
|
|
27
|
+
}
|
|
28
|
+
function recurringNext(value, cadence) {
|
|
29
|
+
const [year, month, day] = value.split("-").map(Number);
|
|
30
|
+
const date = new Date(Date.UTC(year, month - 1, day));
|
|
31
|
+
if (cadence === "Daily")
|
|
32
|
+
date.setUTCDate(date.getUTCDate() + 1);
|
|
33
|
+
else if (cadence === "Every 2 weeks")
|
|
34
|
+
date.setUTCDate(date.getUTCDate() + 14);
|
|
35
|
+
else if (cadence === "Monthly") {
|
|
36
|
+
const targetMonth = month === 12 ? 1 : month + 1;
|
|
37
|
+
const targetYear = month === 12 ? year + 1 : year;
|
|
38
|
+
const lastDay = new Date(Date.UTC(targetYear, targetMonth, 0)).getUTCDate();
|
|
39
|
+
date.setTime(Date.UTC(targetYear, targetMonth - 1, Math.min(day, lastDay)));
|
|
40
|
+
}
|
|
41
|
+
else
|
|
42
|
+
date.setUTCDate(date.getUTCDate() + 7);
|
|
43
|
+
return date.toISOString().slice(0, 10);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=recurring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recurring.js","sourceRoot":"","sources":["../src/recurring.ts"],"names":[],"mappings":"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"}
|