@pgpmjs/core 7.31.0 → 7.32.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/core/ci-matrix.d.ts +15 -0
- package/core/ci-matrix.js +227 -0
- package/esm/core/ci-matrix.js +219 -0
- package/esm/index.js +1 -0
- package/index.d.ts +1 -0
- package/index.js +1 -0
- package/package.json +12 -11
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Insert `entry` into every test matrix of a workflow, keeping the list sorted.
|
|
3
|
+
* Only the byte range of each matrix sequence is rewritten; the rest of the
|
|
4
|
+
* source passes through untouched, so comments and formatting survive.
|
|
5
|
+
*/
|
|
6
|
+
export declare const addToMatrixYaml: (source: string, entry: string) => string;
|
|
7
|
+
/**
|
|
8
|
+
* Add a module to the test matrix of a workspace's CI workflows, keeping the
|
|
9
|
+
* list sorted. The matrix stays a plain, hand-editable array: workflows without
|
|
10
|
+
* a `jobs.<job>.strategy.matrix.package` sequence are left alone.
|
|
11
|
+
* Best-effort: unreadable or unwritable workflows are skipped, never thrown.
|
|
12
|
+
*
|
|
13
|
+
* Returns the workflow files that changed, relative to `workspacePath`.
|
|
14
|
+
*/
|
|
15
|
+
export declare const addToCiMatrix: (workspacePath: string, modulePackagePath: string) => string[];
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.addToCiMatrix = exports.addToMatrixYaml = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const yaml_1 = require("yaml");
|
|
10
|
+
const WORKFLOW_DIR = path_1.default.join('.github', 'workflows');
|
|
11
|
+
const MATRIX_KEY = 'package';
|
|
12
|
+
/** Column the node starts at, i.e. the indent of its first line. */
|
|
13
|
+
const columnOf = (source, offset) => offset - (source.lastIndexOf('\n', offset - 1) + 1);
|
|
14
|
+
/** Every `jobs.<job>.strategy.matrix.package` sequence in a workflow. */
|
|
15
|
+
const findMatrixSeqs = (doc) => {
|
|
16
|
+
const jobs = doc.get('jobs', true);
|
|
17
|
+
if (!(0, yaml_1.isMap)(jobs))
|
|
18
|
+
return [];
|
|
19
|
+
const seqs = [];
|
|
20
|
+
for (const job of jobs.items) {
|
|
21
|
+
if (!(0, yaml_1.isScalar)(job.key))
|
|
22
|
+
continue;
|
|
23
|
+
const seq = doc.getIn(['jobs', job.key.value, 'strategy', 'matrix', MATRIX_KEY], true);
|
|
24
|
+
if ((0, yaml_1.isSeq)(seq))
|
|
25
|
+
seqs.push(seq);
|
|
26
|
+
}
|
|
27
|
+
return seqs;
|
|
28
|
+
};
|
|
29
|
+
/**
|
|
30
|
+
* The sequence's entries with their original source text (so quoting survives),
|
|
31
|
+
* or `null` if any item isn't a plain string scalar.
|
|
32
|
+
*/
|
|
33
|
+
const entriesOf = (seq, source) => {
|
|
34
|
+
const entries = [];
|
|
35
|
+
for (const item of seq.items) {
|
|
36
|
+
if (!(0, yaml_1.isScalar)(item) || typeof item.value !== 'string') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
const range = item.range;
|
|
40
|
+
if (!range)
|
|
41
|
+
return null;
|
|
42
|
+
const [start, end] = range;
|
|
43
|
+
entries.push({
|
|
44
|
+
value: item.value,
|
|
45
|
+
text: source.slice(start, end).trim(),
|
|
46
|
+
comment: item.comment,
|
|
47
|
+
commentBefore: item.commentBefore
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
return entries;
|
|
51
|
+
};
|
|
52
|
+
const commentLines = (comment) => comment ? comment.split('\n').filter((line) => line !== '') : [];
|
|
53
|
+
/** Find standalone comment lines immediately before a node. */
|
|
54
|
+
const commentBlockStart = (source, offset) => {
|
|
55
|
+
let lineStart = source.lastIndexOf('\n', offset - 1) + 1;
|
|
56
|
+
let first;
|
|
57
|
+
while (lineStart > 0) {
|
|
58
|
+
const lineEnd = lineStart - (source[lineStart - 1] === '\n' ? 1 : 0);
|
|
59
|
+
const previousStart = source.lastIndexOf('\n', lineEnd - 1) + 1;
|
|
60
|
+
const line = source.slice(previousStart, lineEnd).replace(/\r$/, '');
|
|
61
|
+
if (!/^\s*#/.test(line))
|
|
62
|
+
break;
|
|
63
|
+
first = previousStart;
|
|
64
|
+
lineStart = previousStart;
|
|
65
|
+
}
|
|
66
|
+
return first;
|
|
67
|
+
};
|
|
68
|
+
const renderCommentBefore = (comment, indent) => commentLines(comment)
|
|
69
|
+
.map((line) => `${indent}#${line}`)
|
|
70
|
+
.join('\n');
|
|
71
|
+
const commentsOf = (doc) => {
|
|
72
|
+
const comments = [];
|
|
73
|
+
const add = (comment, multiline = false) => {
|
|
74
|
+
if (comment === null || comment === undefined)
|
|
75
|
+
return;
|
|
76
|
+
const values = multiline ? comment.split('\n') : [comment];
|
|
77
|
+
comments.push(...values.map((value) => value.trim()));
|
|
78
|
+
};
|
|
79
|
+
add(doc.comment);
|
|
80
|
+
add(doc.commentBefore, true);
|
|
81
|
+
if (doc.contents) {
|
|
82
|
+
(0, yaml_1.visit)(doc.contents, (_key, node) => {
|
|
83
|
+
if (!node || typeof node !== 'object')
|
|
84
|
+
return;
|
|
85
|
+
const commented = node;
|
|
86
|
+
add(commented.comment);
|
|
87
|
+
add(commented.commentBefore, true);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return comments.sort();
|
|
91
|
+
};
|
|
92
|
+
/** Re-render a sequence in the style and at the indent it was written with. */
|
|
93
|
+
const renderSeq = (seq, entries, source, leadingComment = false) => {
|
|
94
|
+
const texts = entries.map((entry) => entry.text);
|
|
95
|
+
if (seq.flow) {
|
|
96
|
+
const original = seq.range
|
|
97
|
+
? source.slice(seq.range[0], seq.range[1])
|
|
98
|
+
: '';
|
|
99
|
+
if (!original.includes('\n'))
|
|
100
|
+
return `[${texts.join(', ')}]`;
|
|
101
|
+
const firstItem = seq.items[0];
|
|
102
|
+
if (!(0, yaml_1.isScalar)(firstItem) || !firstItem.range) {
|
|
103
|
+
return `[${texts.join(', ')}]`;
|
|
104
|
+
}
|
|
105
|
+
const itemIndent = ' '.repeat(columnOf(source, firstItem.range[0]));
|
|
106
|
+
const closing = original.lastIndexOf(']');
|
|
107
|
+
const closingIndent = ' '.repeat(closing >= 0 ? columnOf(source, seq.range[0] + closing) : columnOf(source, seq.range[0]));
|
|
108
|
+
return `[\n${texts
|
|
109
|
+
.map((text, index) => `${itemIndent}${text}${index === texts.length - 1 ? '' : ','}`)
|
|
110
|
+
.join('\n')}\n${closingIndent}]`;
|
|
111
|
+
}
|
|
112
|
+
const indent = ' '.repeat(columnOf(source, seq.range[0]));
|
|
113
|
+
const rendered = entries.map((entry, index) => {
|
|
114
|
+
const before = renderCommentBefore(entry.commentBefore, indent);
|
|
115
|
+
const itemIndent = index === 0 && !entry.commentBefore && !leadingComment ? '' : indent;
|
|
116
|
+
const item = `${itemIndent}- ${entry.text}${entry.comment ? ` #${entry.comment}` : ''}`;
|
|
117
|
+
return before ? `${before}\n${item}` : item;
|
|
118
|
+
});
|
|
119
|
+
return rendered.join('\n');
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Insert `entry` into every test matrix of a workflow, keeping the list sorted.
|
|
123
|
+
* Only the byte range of each matrix sequence is rewritten; the rest of the
|
|
124
|
+
* source passes through untouched, so comments and formatting survive.
|
|
125
|
+
*/
|
|
126
|
+
const addToMatrixYaml = (source, entry) => {
|
|
127
|
+
const doc = (0, yaml_1.parseDocument)(source);
|
|
128
|
+
if (doc.errors.length)
|
|
129
|
+
return source;
|
|
130
|
+
const added = {
|
|
131
|
+
value: entry,
|
|
132
|
+
text: (0, yaml_1.stringify)(entry).trim()
|
|
133
|
+
};
|
|
134
|
+
const edits = [];
|
|
135
|
+
for (const seq of findMatrixSeqs(doc)) {
|
|
136
|
+
const entries = entriesOf(seq, source);
|
|
137
|
+
if (!entries)
|
|
138
|
+
continue;
|
|
139
|
+
if (entries.some((existing) => existing.value === entry))
|
|
140
|
+
continue;
|
|
141
|
+
if (!seq.range)
|
|
142
|
+
continue;
|
|
143
|
+
if (seq.flow &&
|
|
144
|
+
(entries.some((existing) => existing.comment || existing.commentBefore) ||
|
|
145
|
+
seq.comment ||
|
|
146
|
+
seq.commentBefore)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
const [originalStart, end] = seq.range;
|
|
150
|
+
const firstCommentStart = !seq.flow && (seq.commentBefore || entries[0]?.commentBefore)
|
|
151
|
+
? commentBlockStart(source, originalStart)
|
|
152
|
+
: undefined;
|
|
153
|
+
const entriesWithComments = firstCommentStart === undefined || !seq.commentBefore || !entries[0]
|
|
154
|
+
? entries
|
|
155
|
+
: [
|
|
156
|
+
{
|
|
157
|
+
...entries[0],
|
|
158
|
+
commentBefore: entries[0].commentBefore
|
|
159
|
+
? `${seq.commentBefore}\n${entries[0].commentBefore}`
|
|
160
|
+
: seq.commentBefore
|
|
161
|
+
},
|
|
162
|
+
...entries.slice(1)
|
|
163
|
+
];
|
|
164
|
+
const next = [...entriesWithComments, added]
|
|
165
|
+
.filter((item, index, all) => all.findIndex((other) => other.value === item.value) === index)
|
|
166
|
+
.sort((left, right) => left.value.localeCompare(right.value));
|
|
167
|
+
const start = firstCommentStart ?? originalStart;
|
|
168
|
+
// a block sequence's range extends to the next token; keep that whitespace
|
|
169
|
+
const [trailing] = /\s*$/.exec(source.slice(originalStart, end));
|
|
170
|
+
const rendered = renderSeq(seq, next, source, firstCommentStart !== undefined);
|
|
171
|
+
const replacement = rendered + trailing;
|
|
172
|
+
const candidate = source.slice(0, start) + replacement + source.slice(end);
|
|
173
|
+
const candidateDoc = (0, yaml_1.parseDocument)(candidate);
|
|
174
|
+
if (candidateDoc.errors.length ||
|
|
175
|
+
commentsOf(candidateDoc).join('\0') !== commentsOf(doc).join('\0')) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
edits.push({
|
|
179
|
+
start,
|
|
180
|
+
end,
|
|
181
|
+
text: replacement
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
// Last edit first, so earlier ranges keep their offsets.
|
|
185
|
+
return edits
|
|
186
|
+
.sort((left, right) => right.start - left.start)
|
|
187
|
+
.reduce((text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), source);
|
|
188
|
+
};
|
|
189
|
+
exports.addToMatrixYaml = addToMatrixYaml;
|
|
190
|
+
/**
|
|
191
|
+
* Add a module to the test matrix of a workspace's CI workflows, keeping the
|
|
192
|
+
* list sorted. The matrix stays a plain, hand-editable array: workflows without
|
|
193
|
+
* a `jobs.<job>.strategy.matrix.package` sequence are left alone.
|
|
194
|
+
* Best-effort: unreadable or unwritable workflows are skipped, never thrown.
|
|
195
|
+
*
|
|
196
|
+
* Returns the workflow files that changed, relative to `workspacePath`.
|
|
197
|
+
*/
|
|
198
|
+
const addToCiMatrix = (workspacePath, modulePackagePath) => {
|
|
199
|
+
const dir = path_1.default.join(workspacePath, WORKFLOW_DIR);
|
|
200
|
+
const entry = modulePackagePath.split(path_1.default.sep).join('/');
|
|
201
|
+
const changed = [];
|
|
202
|
+
try {
|
|
203
|
+
if (!fs_1.default.existsSync(dir))
|
|
204
|
+
return changed;
|
|
205
|
+
for (const file of fs_1.default.readdirSync(dir).sort()) {
|
|
206
|
+
if (!/\.ya?ml$/.test(file))
|
|
207
|
+
continue;
|
|
208
|
+
try {
|
|
209
|
+
const filePath = path_1.default.join(dir, file);
|
|
210
|
+
const original = fs_1.default.readFileSync(filePath, 'utf8');
|
|
211
|
+
const updated = (0, exports.addToMatrixYaml)(original, entry);
|
|
212
|
+
if (updated === original)
|
|
213
|
+
continue;
|
|
214
|
+
fs_1.default.writeFileSync(filePath, updated);
|
|
215
|
+
changed.push(path_1.default.join(WORKFLOW_DIR, file).split(path_1.default.sep).join('/'));
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return changed;
|
|
224
|
+
}
|
|
225
|
+
return changed;
|
|
226
|
+
};
|
|
227
|
+
exports.addToCiMatrix = addToCiMatrix;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { isMap, isScalar, isSeq, parseDocument, stringify, visit } from 'yaml';
|
|
4
|
+
const WORKFLOW_DIR = path.join('.github', 'workflows');
|
|
5
|
+
const MATRIX_KEY = 'package';
|
|
6
|
+
/** Column the node starts at, i.e. the indent of its first line. */
|
|
7
|
+
const columnOf = (source, offset) => offset - (source.lastIndexOf('\n', offset - 1) + 1);
|
|
8
|
+
/** Every `jobs.<job>.strategy.matrix.package` sequence in a workflow. */
|
|
9
|
+
const findMatrixSeqs = (doc) => {
|
|
10
|
+
const jobs = doc.get('jobs', true);
|
|
11
|
+
if (!isMap(jobs))
|
|
12
|
+
return [];
|
|
13
|
+
const seqs = [];
|
|
14
|
+
for (const job of jobs.items) {
|
|
15
|
+
if (!isScalar(job.key))
|
|
16
|
+
continue;
|
|
17
|
+
const seq = doc.getIn(['jobs', job.key.value, 'strategy', 'matrix', MATRIX_KEY], true);
|
|
18
|
+
if (isSeq(seq))
|
|
19
|
+
seqs.push(seq);
|
|
20
|
+
}
|
|
21
|
+
return seqs;
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The sequence's entries with their original source text (so quoting survives),
|
|
25
|
+
* or `null` if any item isn't a plain string scalar.
|
|
26
|
+
*/
|
|
27
|
+
const entriesOf = (seq, source) => {
|
|
28
|
+
const entries = [];
|
|
29
|
+
for (const item of seq.items) {
|
|
30
|
+
if (!isScalar(item) || typeof item.value !== 'string') {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const range = item.range;
|
|
34
|
+
if (!range)
|
|
35
|
+
return null;
|
|
36
|
+
const [start, end] = range;
|
|
37
|
+
entries.push({
|
|
38
|
+
value: item.value,
|
|
39
|
+
text: source.slice(start, end).trim(),
|
|
40
|
+
comment: item.comment,
|
|
41
|
+
commentBefore: item.commentBefore
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
return entries;
|
|
45
|
+
};
|
|
46
|
+
const commentLines = (comment) => comment ? comment.split('\n').filter((line) => line !== '') : [];
|
|
47
|
+
/** Find standalone comment lines immediately before a node. */
|
|
48
|
+
const commentBlockStart = (source, offset) => {
|
|
49
|
+
let lineStart = source.lastIndexOf('\n', offset - 1) + 1;
|
|
50
|
+
let first;
|
|
51
|
+
while (lineStart > 0) {
|
|
52
|
+
const lineEnd = lineStart - (source[lineStart - 1] === '\n' ? 1 : 0);
|
|
53
|
+
const previousStart = source.lastIndexOf('\n', lineEnd - 1) + 1;
|
|
54
|
+
const line = source.slice(previousStart, lineEnd).replace(/\r$/, '');
|
|
55
|
+
if (!/^\s*#/.test(line))
|
|
56
|
+
break;
|
|
57
|
+
first = previousStart;
|
|
58
|
+
lineStart = previousStart;
|
|
59
|
+
}
|
|
60
|
+
return first;
|
|
61
|
+
};
|
|
62
|
+
const renderCommentBefore = (comment, indent) => commentLines(comment)
|
|
63
|
+
.map((line) => `${indent}#${line}`)
|
|
64
|
+
.join('\n');
|
|
65
|
+
const commentsOf = (doc) => {
|
|
66
|
+
const comments = [];
|
|
67
|
+
const add = (comment, multiline = false) => {
|
|
68
|
+
if (comment === null || comment === undefined)
|
|
69
|
+
return;
|
|
70
|
+
const values = multiline ? comment.split('\n') : [comment];
|
|
71
|
+
comments.push(...values.map((value) => value.trim()));
|
|
72
|
+
};
|
|
73
|
+
add(doc.comment);
|
|
74
|
+
add(doc.commentBefore, true);
|
|
75
|
+
if (doc.contents) {
|
|
76
|
+
visit(doc.contents, (_key, node) => {
|
|
77
|
+
if (!node || typeof node !== 'object')
|
|
78
|
+
return;
|
|
79
|
+
const commented = node;
|
|
80
|
+
add(commented.comment);
|
|
81
|
+
add(commented.commentBefore, true);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return comments.sort();
|
|
85
|
+
};
|
|
86
|
+
/** Re-render a sequence in the style and at the indent it was written with. */
|
|
87
|
+
const renderSeq = (seq, entries, source, leadingComment = false) => {
|
|
88
|
+
const texts = entries.map((entry) => entry.text);
|
|
89
|
+
if (seq.flow) {
|
|
90
|
+
const original = seq.range
|
|
91
|
+
? source.slice(seq.range[0], seq.range[1])
|
|
92
|
+
: '';
|
|
93
|
+
if (!original.includes('\n'))
|
|
94
|
+
return `[${texts.join(', ')}]`;
|
|
95
|
+
const firstItem = seq.items[0];
|
|
96
|
+
if (!isScalar(firstItem) || !firstItem.range) {
|
|
97
|
+
return `[${texts.join(', ')}]`;
|
|
98
|
+
}
|
|
99
|
+
const itemIndent = ' '.repeat(columnOf(source, firstItem.range[0]));
|
|
100
|
+
const closing = original.lastIndexOf(']');
|
|
101
|
+
const closingIndent = ' '.repeat(closing >= 0 ? columnOf(source, seq.range[0] + closing) : columnOf(source, seq.range[0]));
|
|
102
|
+
return `[\n${texts
|
|
103
|
+
.map((text, index) => `${itemIndent}${text}${index === texts.length - 1 ? '' : ','}`)
|
|
104
|
+
.join('\n')}\n${closingIndent}]`;
|
|
105
|
+
}
|
|
106
|
+
const indent = ' '.repeat(columnOf(source, seq.range[0]));
|
|
107
|
+
const rendered = entries.map((entry, index) => {
|
|
108
|
+
const before = renderCommentBefore(entry.commentBefore, indent);
|
|
109
|
+
const itemIndent = index === 0 && !entry.commentBefore && !leadingComment ? '' : indent;
|
|
110
|
+
const item = `${itemIndent}- ${entry.text}${entry.comment ? ` #${entry.comment}` : ''}`;
|
|
111
|
+
return before ? `${before}\n${item}` : item;
|
|
112
|
+
});
|
|
113
|
+
return rendered.join('\n');
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Insert `entry` into every test matrix of a workflow, keeping the list sorted.
|
|
117
|
+
* Only the byte range of each matrix sequence is rewritten; the rest of the
|
|
118
|
+
* source passes through untouched, so comments and formatting survive.
|
|
119
|
+
*/
|
|
120
|
+
export const addToMatrixYaml = (source, entry) => {
|
|
121
|
+
const doc = parseDocument(source);
|
|
122
|
+
if (doc.errors.length)
|
|
123
|
+
return source;
|
|
124
|
+
const added = {
|
|
125
|
+
value: entry,
|
|
126
|
+
text: stringify(entry).trim()
|
|
127
|
+
};
|
|
128
|
+
const edits = [];
|
|
129
|
+
for (const seq of findMatrixSeqs(doc)) {
|
|
130
|
+
const entries = entriesOf(seq, source);
|
|
131
|
+
if (!entries)
|
|
132
|
+
continue;
|
|
133
|
+
if (entries.some((existing) => existing.value === entry))
|
|
134
|
+
continue;
|
|
135
|
+
if (!seq.range)
|
|
136
|
+
continue;
|
|
137
|
+
if (seq.flow &&
|
|
138
|
+
(entries.some((existing) => existing.comment || existing.commentBefore) ||
|
|
139
|
+
seq.comment ||
|
|
140
|
+
seq.commentBefore)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
const [originalStart, end] = seq.range;
|
|
144
|
+
const firstCommentStart = !seq.flow && (seq.commentBefore || entries[0]?.commentBefore)
|
|
145
|
+
? commentBlockStart(source, originalStart)
|
|
146
|
+
: undefined;
|
|
147
|
+
const entriesWithComments = firstCommentStart === undefined || !seq.commentBefore || !entries[0]
|
|
148
|
+
? entries
|
|
149
|
+
: [
|
|
150
|
+
{
|
|
151
|
+
...entries[0],
|
|
152
|
+
commentBefore: entries[0].commentBefore
|
|
153
|
+
? `${seq.commentBefore}\n${entries[0].commentBefore}`
|
|
154
|
+
: seq.commentBefore
|
|
155
|
+
},
|
|
156
|
+
...entries.slice(1)
|
|
157
|
+
];
|
|
158
|
+
const next = [...entriesWithComments, added]
|
|
159
|
+
.filter((item, index, all) => all.findIndex((other) => other.value === item.value) === index)
|
|
160
|
+
.sort((left, right) => left.value.localeCompare(right.value));
|
|
161
|
+
const start = firstCommentStart ?? originalStart;
|
|
162
|
+
// a block sequence's range extends to the next token; keep that whitespace
|
|
163
|
+
const [trailing] = /\s*$/.exec(source.slice(originalStart, end));
|
|
164
|
+
const rendered = renderSeq(seq, next, source, firstCommentStart !== undefined);
|
|
165
|
+
const replacement = rendered + trailing;
|
|
166
|
+
const candidate = source.slice(0, start) + replacement + source.slice(end);
|
|
167
|
+
const candidateDoc = parseDocument(candidate);
|
|
168
|
+
if (candidateDoc.errors.length ||
|
|
169
|
+
commentsOf(candidateDoc).join('\0') !== commentsOf(doc).join('\0')) {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
edits.push({
|
|
173
|
+
start,
|
|
174
|
+
end,
|
|
175
|
+
text: replacement
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
// Last edit first, so earlier ranges keep their offsets.
|
|
179
|
+
return edits
|
|
180
|
+
.sort((left, right) => right.start - left.start)
|
|
181
|
+
.reduce((text, edit) => text.slice(0, edit.start) + edit.text + text.slice(edit.end), source);
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* Add a module to the test matrix of a workspace's CI workflows, keeping the
|
|
185
|
+
* list sorted. The matrix stays a plain, hand-editable array: workflows without
|
|
186
|
+
* a `jobs.<job>.strategy.matrix.package` sequence are left alone.
|
|
187
|
+
* Best-effort: unreadable or unwritable workflows are skipped, never thrown.
|
|
188
|
+
*
|
|
189
|
+
* Returns the workflow files that changed, relative to `workspacePath`.
|
|
190
|
+
*/
|
|
191
|
+
export const addToCiMatrix = (workspacePath, modulePackagePath) => {
|
|
192
|
+
const dir = path.join(workspacePath, WORKFLOW_DIR);
|
|
193
|
+
const entry = modulePackagePath.split(path.sep).join('/');
|
|
194
|
+
const changed = [];
|
|
195
|
+
try {
|
|
196
|
+
if (!fs.existsSync(dir))
|
|
197
|
+
return changed;
|
|
198
|
+
for (const file of fs.readdirSync(dir).sort()) {
|
|
199
|
+
if (!/\.ya?ml$/.test(file))
|
|
200
|
+
continue;
|
|
201
|
+
try {
|
|
202
|
+
const filePath = path.join(dir, file);
|
|
203
|
+
const original = fs.readFileSync(filePath, 'utf8');
|
|
204
|
+
const updated = addToMatrixYaml(original, entry);
|
|
205
|
+
if (updated === original)
|
|
206
|
+
continue;
|
|
207
|
+
fs.writeFileSync(filePath, updated);
|
|
208
|
+
changed.push(path.join(WORKFLOW_DIR, file).split(path.sep).join('/'));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return changed;
|
|
217
|
+
}
|
|
218
|
+
return changed;
|
|
219
|
+
};
|
package/esm/index.js
CHANGED
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.parseAuthor = exports.withTransaction = exports.executeQuery = exports.hashString = exports.hashSqlFile = exports.hashFile = exports.PgpmMigrate = exports.cleanSql = exports.PgpmInit = void 0;
|
|
18
18
|
__exportStar(require("./core/boilerplate-scanner"), exports);
|
|
19
19
|
__exportStar(require("./core/boilerplate-types"), exports);
|
|
20
|
+
__exportStar(require("./core/ci-matrix"), exports);
|
|
20
21
|
__exportStar(require("./core/class/pgpm"), exports);
|
|
21
22
|
__exportStar(require("./core/template-scaffold"), exports);
|
|
22
23
|
__exportStar(require("./diff"), exports);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpmjs/core",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.32.0",
|
|
4
4
|
"author": "Constructive <developers@constructive.io>",
|
|
5
5
|
"description": "PGPM Package and Migration Tools",
|
|
6
6
|
"main": "index.js",
|
|
@@ -49,15 +49,15 @@
|
|
|
49
49
|
"makage": "^0.8.0"
|
|
50
50
|
},
|
|
51
51
|
"dependencies": {
|
|
52
|
-
"@pgpmjs/ast": "^0.15.
|
|
53
|
-
"@pgpmjs/bundle": "^0.18.
|
|
54
|
-
"@pgpmjs/diff": "^0.8.
|
|
55
|
-
"@pgpmjs/env": "^2.45.
|
|
52
|
+
"@pgpmjs/ast": "^0.15.1",
|
|
53
|
+
"@pgpmjs/bundle": "^0.18.1",
|
|
54
|
+
"@pgpmjs/diff": "^0.8.1",
|
|
55
|
+
"@pgpmjs/env": "^2.45.1",
|
|
56
56
|
"@pgpmjs/logger": "^2.26.0",
|
|
57
|
-
"@pgpmjs/server-utils": "^3.29.
|
|
58
|
-
"@pgpmjs/slice": "^0.19.
|
|
59
|
-
"@pgpmjs/transform": "^0.28.
|
|
60
|
-
"@pgpmjs/types": "^2.55.
|
|
57
|
+
"@pgpmjs/server-utils": "^3.29.1",
|
|
58
|
+
"@pgpmjs/slice": "^0.19.1",
|
|
59
|
+
"@pgpmjs/transform": "^0.28.1",
|
|
60
|
+
"@pgpmjs/types": "^2.55.1",
|
|
61
61
|
"cross-spawn": "^7.0.6",
|
|
62
62
|
"csv-to-pg": "^4.14.0",
|
|
63
63
|
"genomic": "^5.6.6",
|
|
@@ -65,11 +65,12 @@
|
|
|
65
65
|
"glob": "^13.0.6",
|
|
66
66
|
"parse-package-name": "^1.0.0",
|
|
67
67
|
"pg": "^8.21.0",
|
|
68
|
-
"pg-cache": "^3.29.
|
|
68
|
+
"pg-cache": "^3.29.1",
|
|
69
69
|
"pg-env": "^1.32.0",
|
|
70
70
|
"pgsql-deparser": "^18.3.7",
|
|
71
71
|
"pgsql-parser": "^18.2.7",
|
|
72
|
+
"yaml": "^2.9.0",
|
|
72
73
|
"yanse": "^0.2.2"
|
|
73
74
|
},
|
|
74
|
-
"gitHead": "
|
|
75
|
+
"gitHead": "0e26550efd7cd782ecaa106be30b3251efd97e6e"
|
|
75
76
|
}
|