@relayfile/adapter-linear 0.3.1 → 0.3.3
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/discovery/linear/.adapter.md +4 -4
- package/dist/digest.d.ts +2 -31
- package/dist/digest.d.ts.map +1 -1
- package/dist/digest.js +15 -150
- package/dist/digest.js.map +1 -1
- package/package.json +2 -2
|
@@ -19,11 +19,11 @@ Resources:
|
|
|
19
19
|
|
|
20
20
|
| To... | Do... |
|
|
21
21
|
|---|---|
|
|
22
|
-
| Read | `cat <id
|
|
23
|
-
| Edit | Write
|
|
24
|
-
| Create | Write JSON to any non-canonical filename such as `create request.json`. The adapter creates the record at
|
|
22
|
+
| Read | `cat <canonical-resource-path>` after listing the resource directory or following an alias when one is available. Use the resource table and ID patterns below to determine whether a resource uses a bare id, an adapter-specific slug/id filename, or an exact sidecar path such as `content.md`. |
|
|
23
|
+
| Edit | Write the resource update payload to the canonical resource path. For JSON resources, included mutable fields PATCH; fields marked `readOnly` in `.schema.json` are rejected. |
|
|
24
|
+
| Create | Write JSON to any non-canonical filename such as `create request.json`. The adapter creates the record at its canonical resource path and rewrites the draft as `{ "created": "<real-id>", "path": "<canonical-resource-path>", "url": "<provider-url>" }`. |
|
|
25
25
|
| Ignore | Editor scratch files named `partial.json`, `.tmp.json`, `.partial.json`, `*.tmp.json`, or `*.partial.json` are ignored and never treated as create drafts. |
|
|
26
|
-
| Delete | `rm <
|
|
26
|
+
| Delete | `rm <canonical-resource-path>` for canonical records. |
|
|
27
27
|
|
|
28
28
|
## ID Patterns
|
|
29
29
|
- `/linear/issues/<id>.json`: `^(?:[A-Za-z0-9_.~-]+(?:--|__))?(?:[0-9a-f]{32}|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$`. Filenames that do not match this pattern are treated as create drafts.
|
package/dist/digest.d.ts
CHANGED
|
@@ -1,33 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
readonly to: string;
|
|
4
|
-
}
|
|
5
|
-
export interface DigestChangeEvent {
|
|
6
|
-
readonly id?: string;
|
|
7
|
-
readonly timestamp?: string;
|
|
8
|
-
readonly occurredAt?: string;
|
|
9
|
-
readonly eventType?: string;
|
|
10
|
-
readonly type?: string;
|
|
11
|
-
readonly action?: string;
|
|
12
|
-
readonly canonicalPath?: string;
|
|
13
|
-
readonly path?: string;
|
|
14
|
-
}
|
|
15
|
-
export interface DigestContext {
|
|
16
|
-
readonly provider: string;
|
|
17
|
-
readonly window: DigestWindow;
|
|
18
|
-
changeEvents(filter?: {
|
|
19
|
-
providers?: string[];
|
|
20
|
-
paths?: string[];
|
|
21
|
-
}): Promise<readonly DigestChangeEvent[]>;
|
|
22
|
-
}
|
|
23
|
-
export interface DigestBullet {
|
|
24
|
-
readonly text: string;
|
|
25
|
-
readonly canonicalPath: string;
|
|
26
|
-
}
|
|
27
|
-
export interface DigestSection {
|
|
28
|
-
readonly provider: string;
|
|
29
|
-
readonly bullets: readonly DigestBullet[];
|
|
30
|
-
}
|
|
31
|
-
export type DigestHandler = (ctx: DigestContext) => Promise<DigestSection | null>;
|
|
1
|
+
import { type DigestBullet, type DigestChangeEvent, type DigestContext, type DigestHandler, type DigestSection, type DigestWindow } from "@relayfile/adapter-core";
|
|
2
|
+
export type { DigestBullet, DigestChangeEvent, DigestContext, DigestHandler, DigestSection, DigestWindow, };
|
|
32
3
|
export declare const digest: DigestHandler;
|
|
33
4
|
//# sourceMappingURL=digest.d.ts.map
|
package/dist/digest.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"digest.d.ts","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,yBAAyB,CAAC;AAEjC,YAAY,EACV,YAAY,EACZ,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,aAAa,EACb,YAAY,GACb,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,aAUnB,CAAC"}
|
package/dist/digest.js
CHANGED
|
@@ -1,154 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
});
|
|
14
|
-
return bullets.length === 0 ? null : { provider: ctx.provider, bullets };
|
|
15
|
-
};
|
|
16
|
-
function hasDigestPath(event) {
|
|
17
|
-
return (typeof digestEventPath(event) === 'string'
|
|
18
|
-
&& isCanonicalDigestPath(digestEventPath(event))
|
|
19
|
-
&& (digestEventPath(event) === 'linear' || digestEventPath(event) === '/linear' || digestEventPath(event).startsWith('linear/') || digestEventPath(event).startsWith('/linear/')));
|
|
20
|
-
}
|
|
21
|
-
function isCanonicalDigestPath(path) {
|
|
22
|
-
const segments = normalizeDigestPath(path).split('/').filter(Boolean);
|
|
23
|
-
const leaf = segments.at(-1) ?? '';
|
|
24
|
-
return leaf !== 'LAYOUT.md'
|
|
25
|
-
&& leaf !== '_index.json'
|
|
26
|
-
&& !hasDigestAliasDirectory(segments);
|
|
27
|
-
}
|
|
28
|
-
const DIGEST_ALIAS_PROVIDER_SEGMENTS = new Set([
|
|
29
|
-
'asana',
|
|
30
|
-
'clickup',
|
|
31
|
-
'confluence',
|
|
32
|
-
'github',
|
|
33
|
-
'gitlab',
|
|
34
|
-
'jira',
|
|
35
|
-
'linear',
|
|
36
|
-
'notion',
|
|
37
|
-
'slack',
|
|
38
|
-
]);
|
|
39
|
-
const DIGEST_ALIAS_SEGMENTS = new Set([
|
|
40
|
-
'by-assignee',
|
|
41
|
-
'by-creator',
|
|
42
|
-
'by-database',
|
|
43
|
-
'by-id',
|
|
44
|
-
'by-key',
|
|
45
|
-
'by-name',
|
|
46
|
-
'by-parent',
|
|
47
|
-
'by-priority',
|
|
48
|
-
'by-ref',
|
|
49
|
-
'by-space',
|
|
50
|
-
'by-state',
|
|
51
|
-
'by-status',
|
|
52
|
-
'by-title',
|
|
53
|
-
'by-uuid',
|
|
54
|
-
]);
|
|
55
|
-
const DIGEST_ALIAS_PARENT_SEGMENTS = new Set([
|
|
56
|
-
'channels',
|
|
57
|
-
'commits',
|
|
58
|
-
'databases',
|
|
59
|
-
'deployments',
|
|
60
|
-
'issues',
|
|
61
|
-
'pages',
|
|
62
|
-
'pipelines',
|
|
63
|
-
'projects',
|
|
64
|
-
'pulls',
|
|
65
|
-
'sprints',
|
|
66
|
-
'spaces',
|
|
67
|
-
'tags',
|
|
68
|
-
'tasks',
|
|
69
|
-
'teams',
|
|
70
|
-
'users',
|
|
71
|
-
]);
|
|
72
|
-
function hasDigestAliasDirectory(segments) {
|
|
73
|
-
const provider = segments[0] ?? '';
|
|
74
|
-
if (!DIGEST_ALIAS_PROVIDER_SEGMENTS.has(provider))
|
|
75
|
-
return false;
|
|
76
|
-
for (let index = 1; index < segments.length - 1; index += 1) {
|
|
77
|
-
const segment = segments[index];
|
|
78
|
-
const parent = segments[index - 1];
|
|
79
|
-
if (segment && parent && DIGEST_ALIAS_SEGMENTS.has(segment) && DIGEST_ALIAS_PARENT_SEGMENTS.has(parent)) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
function compareEvents(left, right) {
|
|
86
|
-
// Compare parsed timestamps in ms rather than ISO strings: lexicographic
|
|
87
|
-
// string compare misorders events whose timestamps describe the same
|
|
88
|
-
// instant with different textual offsets (e.g. `Z` vs `+00:00`).
|
|
89
|
-
const leftMs = eventTimeMs(left);
|
|
90
|
-
const rightMs = eventTimeMs(right);
|
|
91
|
-
return (leftMs - rightMs
|
|
92
|
-
|| compareDigestStrings(left.id ?? '', right.id ?? '')
|
|
93
|
-
|| compareDigestStrings(digestEventPath(left) ?? '', digestEventPath(right) ?? ''));
|
|
94
|
-
}
|
|
95
|
-
function compareDigestStrings(left, right) {
|
|
96
|
-
if (left < right)
|
|
97
|
-
return -1;
|
|
98
|
-
if (left > right)
|
|
99
|
-
return 1;
|
|
100
|
-
return 0;
|
|
101
|
-
}
|
|
102
|
-
function eventTime(event) {
|
|
103
|
-
return event.timestamp ?? event.occurredAt ?? '';
|
|
104
|
-
}
|
|
105
|
-
function eventTimeMs(event) {
|
|
106
|
-
const raw = eventTime(event);
|
|
107
|
-
if (!raw)
|
|
108
|
-
return Number.NEGATIVE_INFINITY;
|
|
109
|
-
const ms = Date.parse(raw);
|
|
110
|
-
return Number.isNaN(ms) ? Number.NEGATIVE_INFINITY : ms;
|
|
111
|
-
}
|
|
112
|
-
function digestEventPath(event) {
|
|
113
|
-
return event.canonicalPath ?? event.path ?? '';
|
|
114
|
-
}
|
|
115
|
-
function normalizeDigestPath(path) {
|
|
116
|
-
return path.replace(/^\/+/u, '');
|
|
117
|
-
}
|
|
1
|
+
import { createDigestHandler, } from "@relayfile/adapter-core";
|
|
2
|
+
export const digest = createDigestHandler({
|
|
3
|
+
provider: "linear",
|
|
4
|
+
identify: linearIdentifier,
|
|
5
|
+
actionRules: [
|
|
6
|
+
{ verbs: "create|created|open|opened|add|added|write|written", pastTense: "was created" },
|
|
7
|
+
{ verbs: "delete|deleted|remove|removed", pastTense: "was deleted" },
|
|
8
|
+
{ verbs: "cancel|canceled|cancelled", pastTense: "was canceled" },
|
|
9
|
+
{ verbs: "complete|completed|done", pastTense: "was completed" },
|
|
10
|
+
{ verbs: "close|closed|resolve|resolved", pastTense: "was closed" },
|
|
11
|
+
],
|
|
12
|
+
});
|
|
118
13
|
function linearIdentifier(path) {
|
|
119
|
-
const segment = path.split(
|
|
120
|
-
const basename = segment.replace(/\.[^.]+$/u,
|
|
121
|
-
const separatorIndex = basename.lastIndexOf(
|
|
14
|
+
const segment = path.split("/").filter(Boolean).at(-1) ?? path;
|
|
15
|
+
const basename = segment.replace(/\.[^.]+$/u, "");
|
|
16
|
+
const separatorIndex = basename.lastIndexOf("__");
|
|
122
17
|
return separatorIndex > 0 ? basename.slice(0, separatorIndex) : basename;
|
|
123
18
|
}
|
|
124
|
-
const ACTION_VERB_PATTERN_1 = actionVerbRegex('create|created|open|opened|add|added|write|written');
|
|
125
|
-
const ACTION_VERB_PATTERN_2 = actionVerbRegex('delete|deleted|remove|removed');
|
|
126
|
-
const ACTION_VERB_PATTERN_3 = actionVerbRegex('cancel|canceled|cancelled');
|
|
127
|
-
const ACTION_VERB_PATTERN_4 = actionVerbRegex('complete|completed|done');
|
|
128
|
-
const ACTION_VERB_PATTERN_5 = actionVerbRegex('close|closed|resolve|resolved');
|
|
129
|
-
function pastTense(event) {
|
|
130
|
-
const action = (event.action ?? event.eventType ?? event.type ?? '').toLowerCase();
|
|
131
|
-
if (hasActionVerb(action, ACTION_VERB_PATTERN_1)) {
|
|
132
|
-
return 'was created';
|
|
133
|
-
}
|
|
134
|
-
if (hasActionVerb(action, ACTION_VERB_PATTERN_2)) {
|
|
135
|
-
return 'was deleted';
|
|
136
|
-
}
|
|
137
|
-
if (hasActionVerb(action, ACTION_VERB_PATTERN_3)) {
|
|
138
|
-
return 'was canceled';
|
|
139
|
-
}
|
|
140
|
-
if (hasActionVerb(action, ACTION_VERB_PATTERN_4)) {
|
|
141
|
-
return 'was completed';
|
|
142
|
-
}
|
|
143
|
-
if (hasActionVerb(action, ACTION_VERB_PATTERN_5)) {
|
|
144
|
-
return 'was closed';
|
|
145
|
-
}
|
|
146
|
-
return 'was updated';
|
|
147
|
-
}
|
|
148
|
-
function actionVerbRegex(verbs) {
|
|
149
|
-
return new RegExp(`(^|[^a-z0-9])(${verbs})([^a-z0-9]|$)`, 'u');
|
|
150
|
-
}
|
|
151
|
-
function hasActionVerb(action, pattern) {
|
|
152
|
-
return pattern.test(action);
|
|
153
|
-
}
|
|
154
19
|
//# sourceMappingURL=digest.js.map
|
package/dist/digest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,GAOpB,MAAM,yBAAyB,CAAC;AAWjC,MAAM,CAAC,MAAM,MAAM,GAAkB,mBAAmB,CAAC;IACvD,QAAQ,EAAE,QAAQ;IAClB,QAAQ,EAAE,gBAAgB;IAC1B,WAAW,EAAE;QACX,EAAE,KAAK,EAAE,oDAAoD,EAAE,SAAS,EAAE,aAAa,EAAE;QACzF,EAAE,KAAK,EAAE,+BAA+B,EAAE,SAAS,EAAE,aAAa,EAAE;QACpE,EAAE,KAAK,EAAE,2BAA2B,EAAE,SAAS,EAAE,cAAc,EAAE;QACjE,EAAE,KAAK,EAAE,yBAAyB,EAAE,SAAS,EAAE,eAAe,EAAE;QAChE,EAAE,KAAK,EAAE,+BAA+B,EAAE,SAAS,EAAE,YAAY,EAAE;KACpE;CACF,CAAC,CAAC;AAEH,SAAS,gBAAgB,CAAC,IAAY;IACpC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAClD,MAAM,cAAc,GAAG,QAAQ,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC3E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/adapter-linear",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Linear adapter bootstrap package for Relayfile",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"directory": "packages/linear"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@relayfile/adapter-core": "^0.3.
|
|
58
|
+
"@relayfile/adapter-core": "^0.3.9"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@relayfile/sdk": ">=0.6.0 <1"
|