@dominic.mayers/last-of-readme 0.1.21 → 0.1.22
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/README.md +1 -1
- package/package.json +4 -2
- package/scripts/collect-user-input.cjs +56 -11
- package/scripts/install-doc-link.cjs +35 -62
- package/scripts/local-workspace-adapter.cjs +70 -21
- package/scripts/update-readme-link.cjs +18 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Last of README
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@dominic.mayers/last-of-readme) <!-- DOC-LINK-START --><a href="https://dominic-mayers.github.io/last-of-readme/readme-resolver.html?mode=last&pkg=%40dominic.mayers%2Flast-of-readme&repo=Dominic-Mayers%2Flast-of-readme&host=github.com&v=0.1.
|
|
3
|
+
[](https://www.npmjs.com/package/@dominic.mayers/last-of-readme) <!-- DOC-LINK-START --><a href="https://dominic-mayers.github.io/last-of-readme/readme-resolver.html?mode=last&pkg=%40dominic.mayers%2Flast-of-readme&repo=Dominic-Mayers%2Flast-of-readme&host=github.com&v=0.1.22&urlPath="><img alt="README-last of 0.1.22" src="https://img.shields.io/badge/README-last%20of%200.1.22-blue?logo=github"></a><!-- DOC-LINK-END -->
|
|
4
4
|
|
|
5
5
|
By default, a package version is expected to contain its correct documentation, but in practice there is no guarantee that documentation is complete or even correct at release time.
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dominic.mayers/last-of-readme",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.22",
|
|
4
4
|
"description": "Resolve README to last relevant commit based on npm version",
|
|
5
5
|
"author": "Dominic Mayers",
|
|
6
6
|
"license": "MIT",
|
|
@@ -25,6 +25,8 @@
|
|
|
25
25
|
"kind": "github",
|
|
26
26
|
"host": "github.com",
|
|
27
27
|
"repository": "Dominic-Mayers/last-of-readme"
|
|
28
|
-
}
|
|
28
|
+
},
|
|
29
|
+
"packageFilePath": "README.md",
|
|
30
|
+
"repositoryUrlPath": ""
|
|
29
31
|
}
|
|
30
32
|
}
|
|
@@ -1,22 +1,65 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const path = require('path');
|
|
3
|
+
const { execFileSync } = require('child_process');
|
|
5
4
|
const readline = require('readline');
|
|
6
5
|
const { listRemoteChoices } = require('./install-remote.cjs');
|
|
7
6
|
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
function runNpmPkg(args, { allowFailure = false } = {}) {
|
|
8
|
+
try {
|
|
9
|
+
return execFileSync('npm', ['pkg', ...args], {
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
encoding: 'utf8',
|
|
12
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
13
|
+
}).trim();
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (allowFailure) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const stderr = String(error.stderr || '').trim();
|
|
20
|
+
const suffix = stderr ? `: ${stderr}` : '';
|
|
21
|
+
throw new Error(`Could not access package.json through npm pkg${suffix}`);
|
|
12
22
|
}
|
|
23
|
+
}
|
|
13
24
|
|
|
25
|
+
function getPackageJsonField(fieldPath) {
|
|
26
|
+
const raw = runNpmPkg(['get', fieldPath, '--json'], { allowFailure: true });
|
|
27
|
+
if (raw === null || raw === '') {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let parsed;
|
|
14
32
|
try {
|
|
15
|
-
|
|
16
|
-
return JSON.parse(raw);
|
|
33
|
+
parsed = JSON.parse(raw);
|
|
17
34
|
} catch (error) {
|
|
18
|
-
throw new Error(`Could not
|
|
35
|
+
throw new Error(`Could not parse npm pkg output for ${fieldPath}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return getNestedValue(parsed, fieldPath);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getNestedValue(value, fieldPath) {
|
|
42
|
+
return fieldPath
|
|
43
|
+
.split('.')
|
|
44
|
+
.reduce(
|
|
45
|
+
(current, key) =>
|
|
46
|
+
current && typeof current === 'object' && key in current ? current[key] : undefined,
|
|
47
|
+
value
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadPackageJson() {
|
|
52
|
+
const versionScript = getPackageJsonField('scripts.version');
|
|
53
|
+
|
|
54
|
+
if (typeof versionScript === 'undefined') {
|
|
55
|
+
return null;
|
|
19
56
|
}
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
scripts: {
|
|
60
|
+
version: versionScript,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
20
63
|
}
|
|
21
64
|
|
|
22
65
|
function parsePackageFilePathFromVersionScript(versionScript) {
|
|
@@ -28,7 +71,7 @@ function parsePackageFilePathFromVersionScript(versionScript) {
|
|
|
28
71
|
/node\s+scripts\/update-readme-link\.cjs\s+(?:'([^']+)'|"([^"]+)"|([^\s]+))/
|
|
29
72
|
);
|
|
30
73
|
|
|
31
|
-
return match ?
|
|
74
|
+
return match ? match[1] || match[2] || match[3] : null;
|
|
32
75
|
}
|
|
33
76
|
|
|
34
77
|
function parseRepositoryUrlPathFromVersionScript(versionScript) {
|
|
@@ -40,7 +83,7 @@ function parseRepositoryUrlPathFromVersionScript(versionScript) {
|
|
|
40
83
|
/node\s+scripts\/update-readme-link\.cjs\s+(?:'[^']+'|"[^"]+"|[^\s]+)\s+(?:'([^']*)'|"([^"]*)"|([^\s]+))/
|
|
41
84
|
);
|
|
42
85
|
|
|
43
|
-
return match ?
|
|
86
|
+
return match ? match[1] || match[2] || match[3] || '' : null;
|
|
44
87
|
}
|
|
45
88
|
|
|
46
89
|
function createInterface() {
|
|
@@ -232,4 +275,6 @@ module.exports = {
|
|
|
232
275
|
loadPackageJson,
|
|
233
276
|
parsePackageFilePathFromVersionScript,
|
|
234
277
|
parseRepositoryUrlPathFromVersionScript,
|
|
278
|
+
runNpmPkg,
|
|
279
|
+
getPackageJsonField,
|
|
235
280
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { execFileSync } = require('child_process');
|
|
4
3
|
const fs = require('fs');
|
|
5
4
|
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
6
|
|
|
7
7
|
const START_MARKER = '<!-- DOC-LINK-START -->';
|
|
8
8
|
const END_MARKER = '<!-- DOC-LINK-END -->';
|
|
@@ -188,67 +188,41 @@ function installDocLink(config = {}) {
|
|
|
188
188
|
};
|
|
189
189
|
}
|
|
190
190
|
|
|
191
|
-
function runNpmPkg(args,
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
197
|
-
}).trim();
|
|
198
|
-
} catch (error) {
|
|
199
|
-
const details = [error.stderr, error.stdout]
|
|
200
|
-
.filter(Boolean)
|
|
201
|
-
.map((value) => String(value).trim())
|
|
202
|
-
.find(Boolean);
|
|
191
|
+
function runNpmPkg(args, failureMessage) {
|
|
192
|
+
const result = spawnSync('npm', ['pkg', ...args], {
|
|
193
|
+
cwd: process.cwd(),
|
|
194
|
+
encoding: 'utf8',
|
|
195
|
+
});
|
|
203
196
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
);
|
|
197
|
+
if (result.error) {
|
|
198
|
+
throw new Error(`${failureMessage}: ${result.error.message}`);
|
|
207
199
|
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function readPackageJson() {
|
|
211
|
-
const raw = runNpmPkg(['get', '--json'], 'Could not read package.json');
|
|
212
200
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
throw new Error(`Could not parse package.json content from npm pkg get: ${error.message}`);
|
|
201
|
+
if (result.status !== 0) {
|
|
202
|
+
const suffix = (result.stderr || result.stdout || '').trim();
|
|
203
|
+
throw new Error(`${failureMessage}${suffix ? `: ${suffix}` : ''}`);
|
|
217
204
|
}
|
|
205
|
+
|
|
206
|
+
return (result.stdout || '').trim();
|
|
218
207
|
}
|
|
219
208
|
|
|
220
209
|
function getPackageJsonField(field) {
|
|
221
210
|
const raw = runNpmPkg(['get', field, '--json'], `Could not read package.json field "${field}"`);
|
|
222
211
|
|
|
223
|
-
if (!raw || raw === 'undefined') {
|
|
224
|
-
return undefined;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
let parsed;
|
|
228
212
|
try {
|
|
229
|
-
parsed = JSON.parse(raw);
|
|
213
|
+
const parsed = JSON.parse(raw);
|
|
214
|
+
return Array.isArray(parsed) && parsed.length === 1 ? parsed[0] : parsed;
|
|
230
215
|
} catch (error) {
|
|
231
216
|
throw new Error(
|
|
232
217
|
`Could not parse package.json field "${field}" from npm pkg get: ${error.message}`
|
|
233
218
|
);
|
|
234
219
|
}
|
|
235
|
-
|
|
236
|
-
if (
|
|
237
|
-
parsed &&
|
|
238
|
-
typeof parsed === 'object' &&
|
|
239
|
-
!Array.isArray(parsed) &&
|
|
240
|
-
Object.prototype.hasOwnProperty.call(parsed, field)
|
|
241
|
-
) {
|
|
242
|
-
return parsed[field];
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
return parsed;
|
|
246
220
|
}
|
|
247
221
|
|
|
248
|
-
function setPackageJsonFields(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
222
|
+
function setPackageJsonFields(assignments) {
|
|
223
|
+
if (!Array.isArray(assignments) || assignments.length === 0) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
252
226
|
|
|
253
227
|
runNpmPkg(['set', '--json', ...assignments], 'Could not update package.json');
|
|
254
228
|
}
|
|
@@ -274,20 +248,20 @@ function installDocLinkPackageJson(config = {}) {
|
|
|
274
248
|
throw new Error('Doc-link package.json installation requires resolved doc-link cycle state');
|
|
275
249
|
}
|
|
276
250
|
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
251
|
+
const assignments = [
|
|
252
|
+
`lastOfReadme.packageFilePath=${JSON.stringify(docLink.packageFilePath)}`,
|
|
253
|
+
`lastOfReadme.repositoryUrlPath=${JSON.stringify(docLink.repositoryUrlPath || '')}`,
|
|
254
|
+
`scripts.version=${JSON.stringify(
|
|
255
|
+
buildVersionScript(docLink.packageFilePath, docLink.repositoryUrlPath)
|
|
256
|
+
)}`,
|
|
257
|
+
];
|
|
283
258
|
|
|
284
|
-
|
|
285
|
-
if (files !== undefined) {
|
|
286
|
-
if (!Array.isArray(files)) {
|
|
287
|
-
throw new Error('package.json.files must be an array when present');
|
|
288
|
-
}
|
|
259
|
+
setPackageJsonFields(assignments);
|
|
289
260
|
|
|
261
|
+
const files = getPackageJsonField('files');
|
|
262
|
+
if (Array.isArray(files)) {
|
|
290
263
|
const nextFiles = [...files];
|
|
264
|
+
|
|
291
265
|
if (!nextFiles.includes(docLink.packageFilePath)) {
|
|
292
266
|
nextFiles.push(docLink.packageFilePath);
|
|
293
267
|
}
|
|
@@ -297,16 +271,15 @@ function installDocLinkPackageJson(config = {}) {
|
|
|
297
271
|
docLink.previousPackageFilePath &&
|
|
298
272
|
docLink.previousPackageFilePath !== docLink.packageFilePath
|
|
299
273
|
) {
|
|
300
|
-
|
|
274
|
+
const filteredFiles = nextFiles.filter(
|
|
301
275
|
(item) => item !== docLink.previousPackageFilePath
|
|
302
276
|
);
|
|
303
|
-
|
|
304
|
-
|
|
277
|
+
setPackageJsonFields([`files=${JSON.stringify(filteredFiles)}`]);
|
|
278
|
+
} else if (nextFiles.length !== files.length) {
|
|
279
|
+
setPackageJsonFields([`files=${JSON.stringify(nextFiles)}`]);
|
|
305
280
|
}
|
|
306
281
|
}
|
|
307
282
|
|
|
308
|
-
setPackageJsonFields(updates);
|
|
309
|
-
|
|
310
283
|
return {
|
|
311
284
|
path: 'package.json',
|
|
312
285
|
packageFilePath: docLink.packageFilePath,
|
|
@@ -325,6 +298,6 @@ module.exports = {
|
|
|
325
298
|
installDocLink,
|
|
326
299
|
checkDocLinkPackageJsonRequirements,
|
|
327
300
|
installDocLinkPackageJson,
|
|
328
|
-
|
|
301
|
+
getPackageJsonField,
|
|
329
302
|
buildVersionScript,
|
|
330
303
|
};
|
|
@@ -21,6 +21,44 @@ function run(command, options = {}) {
|
|
|
21
21
|
}).trim();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function runNpmPkg(args) {
|
|
25
|
+
const result = cp.spawnSync('npm', ['pkg', ...args], {
|
|
26
|
+
cwd: WORKSPACE_ROOT,
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
if (result.error) {
|
|
31
|
+
fail(`Could not run npm pkg ${args.join(' ')}: ${result.error.message}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (result.status !== 0) {
|
|
35
|
+
const detail = (result.stderr || result.stdout || '').trim();
|
|
36
|
+
fail(`npm pkg ${args.join(' ')} failed${detail ? `: ${detail}` : ''}`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const stdout = (result.stdout || '').trim();
|
|
40
|
+
if (!stdout) {
|
|
41
|
+
fail(`npm pkg ${args.join(' ')} returned no output`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
return JSON.parse(stdout);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
fail(`Could not parse npm pkg ${args.join(' ')} output: ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function getPackageJsonField(field, { allowEmpty = false } = {}) {
|
|
52
|
+
const value = runNpmPkg(['get', field, '--json']);
|
|
53
|
+
const normalized = Array.isArray(value) && value.length === 1 ? value[0] : value;
|
|
54
|
+
|
|
55
|
+
if ((normalized === undefined || normalized === null || normalized === '') && !allowEmpty) {
|
|
56
|
+
fail(`package.json has no ${field}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return normalized;
|
|
60
|
+
}
|
|
61
|
+
|
|
24
62
|
function resolveWorkspacePath(relativePath) {
|
|
25
63
|
if (!relativePath) {
|
|
26
64
|
fail('Path is required');
|
|
@@ -37,7 +75,8 @@ function ensureFile(filePath, label) {
|
|
|
37
75
|
function readPackageJson() {
|
|
38
76
|
ensureFile(PACKAGE_PATH, 'package.json');
|
|
39
77
|
try {
|
|
40
|
-
|
|
78
|
+
const result = runNpmPkg(['get', '--json']);
|
|
79
|
+
return Array.isArray(result) && result.length === 1 ? result[0] : result;
|
|
41
80
|
} catch (err) {
|
|
42
81
|
fail(`Could not read package.json: ${err.message}`);
|
|
43
82
|
}
|
|
@@ -97,45 +136,37 @@ function currentRepoNode() {
|
|
|
97
136
|
}
|
|
98
137
|
|
|
99
138
|
function currentPackageVersion() {
|
|
100
|
-
|
|
101
|
-
if (!pkg.version) {
|
|
102
|
-
fail('package.json has no version');
|
|
103
|
-
}
|
|
104
|
-
return pkg.version;
|
|
139
|
+
return String(getPackageJsonField('version'));
|
|
105
140
|
}
|
|
106
141
|
|
|
107
142
|
function packageName() {
|
|
108
|
-
|
|
109
|
-
if (!pkg.name) {
|
|
110
|
-
fail('package.json has no name');
|
|
111
|
-
}
|
|
112
|
-
return pkg.name;
|
|
143
|
+
return String(getPackageJsonField('name'));
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
function remoteConfiguration() {
|
|
116
|
-
const
|
|
117
|
-
const
|
|
147
|
+
const kind = getPackageJsonField('lastOfReadme.remote.kind', { allowEmpty: true });
|
|
148
|
+
const host = getPackageJsonField('lastOfReadme.remote.host', { allowEmpty: true });
|
|
149
|
+
const repository = getPackageJsonField('lastOfReadme.remote.repository', { allowEmpty: true });
|
|
118
150
|
|
|
119
|
-
if (
|
|
120
|
-
if (
|
|
151
|
+
if (kind !== undefined && kind !== null && kind !== '') {
|
|
152
|
+
if (kind !== 'github') {
|
|
121
153
|
fail('package.json lastOfReadme.remote.kind must be "github"');
|
|
122
154
|
}
|
|
123
|
-
if (!
|
|
155
|
+
if (!host || !repository) {
|
|
124
156
|
fail('package.json lastOfReadme.remote must include host and repository');
|
|
125
157
|
}
|
|
126
158
|
return {
|
|
127
159
|
kind: 'github',
|
|
128
|
-
host: String(
|
|
129
|
-
repository: String(
|
|
160
|
+
host: String(host),
|
|
161
|
+
repository: String(repository),
|
|
130
162
|
};
|
|
131
163
|
}
|
|
132
164
|
|
|
133
|
-
return normalizeRepositoryUrl(
|
|
165
|
+
return normalizeRepositoryUrl(getPackageJsonField('repository'));
|
|
134
166
|
}
|
|
135
167
|
|
|
136
168
|
function remoteName() {
|
|
137
|
-
const
|
|
138
|
-
const configuredRemoteName = pkg.lastOfReadme && pkg.lastOfReadme.remoteName;
|
|
169
|
+
const configuredRemoteName = getPackageJsonField('lastOfReadme.remoteName', { allowEmpty: true });
|
|
139
170
|
if (configuredRemoteName && typeof configuredRemoteName === 'string') {
|
|
140
171
|
return configuredRemoteName;
|
|
141
172
|
}
|
|
@@ -146,6 +177,22 @@ function remoteRepository() {
|
|
|
146
177
|
return remoteConfiguration().repository;
|
|
147
178
|
}
|
|
148
179
|
|
|
180
|
+
function packageFilePath() {
|
|
181
|
+
const value = getPackageJsonField('lastOfReadme.packageFilePath', { allowEmpty: true });
|
|
182
|
+
if (!value || typeof value !== 'string') {
|
|
183
|
+
fail('package.json has no lastOfReadme.packageFilePath');
|
|
184
|
+
}
|
|
185
|
+
return value;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function repositoryUrlPath() {
|
|
189
|
+
const value = getPackageJsonField('lastOfReadme.repositoryUrlPath', { allowEmpty: true });
|
|
190
|
+
if (value === undefined || value === null) {
|
|
191
|
+
fail('package.json has no lastOfReadme.repositoryUrlPath');
|
|
192
|
+
}
|
|
193
|
+
return String(value);
|
|
194
|
+
}
|
|
195
|
+
|
|
149
196
|
function readFile(relativePath) {
|
|
150
197
|
const filePath = resolveWorkspacePath(relativePath);
|
|
151
198
|
ensureFile(filePath, relativePath);
|
|
@@ -201,4 +248,6 @@ module.exports = {
|
|
|
201
248
|
packageName,
|
|
202
249
|
currentPackageVersion,
|
|
203
250
|
remoteName,
|
|
251
|
+
packageFilePath,
|
|
252
|
+
repositoryUrlPath,
|
|
204
253
|
};
|
|
@@ -21,6 +21,23 @@ function usage() {
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
function resolveInputs() {
|
|
25
|
+
const cliDocumentationPath = process.argv[2];
|
|
26
|
+
const cliUrlPath = process.argv[3] || '';
|
|
27
|
+
|
|
28
|
+
if (cliDocumentationPath) {
|
|
29
|
+
return {
|
|
30
|
+
documentationPath: cliDocumentationPath,
|
|
31
|
+
urlPath: cliUrlPath,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
documentationPath: workspace.packageFilePath(),
|
|
37
|
+
urlPath: workspace.repositoryUrlPath(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
function buildResolverLink(urlPath = '') {
|
|
25
42
|
const version = workspace.currentPackageVersion();
|
|
26
43
|
const packageName = workspace.packageName();
|
|
@@ -87,12 +104,7 @@ function replaceManagedBlock(content, replacement) {
|
|
|
87
104
|
}
|
|
88
105
|
|
|
89
106
|
function main() {
|
|
90
|
-
const documentationPath =
|
|
91
|
-
const urlPath = process.argv[3] || '';
|
|
92
|
-
|
|
93
|
-
if (!documentationPath) {
|
|
94
|
-
usage();
|
|
95
|
-
}
|
|
107
|
+
const { documentationPath, urlPath } = resolveInputs();
|
|
96
108
|
|
|
97
109
|
try {
|
|
98
110
|
const link = buildResolverLink(urlPath);
|