@k8slens/package-build 3.3.0 → 3.4.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/bin/extension-publish-check +5 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +194 -91
- package/dist/src/extension-dependencies-check.d.ts +5 -2
- package/dist/src/extension-dependencies-check.d.ts.map +1 -1
- package/dist/src/extension-publish-check.d.ts +42 -0
- package/dist/src/extension-publish-check.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -2
- package/src/extension-dependencies-check.ts +13 -2
- package/src/extension-publish-check.test.ts +286 -0
- package/src/extension-publish-check.ts +177 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k8slens/package-build",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.4.0",
|
|
4
4
|
"lens": {
|
|
5
5
|
"env": "agnostic",
|
|
6
6
|
"layer": "infrastructure",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
},
|
|
43
43
|
"bin": {
|
|
44
44
|
"lens-package-build": "bin/package-build",
|
|
45
|
-
"lens-extension-dependencies-check": "bin/extension-dependencies-check"
|
|
45
|
+
"lens-extension-dependencies-check": "bin/extension-dependencies-check",
|
|
46
|
+
"lens-extension-publish-check": "bin/extension-publish-check"
|
|
46
47
|
},
|
|
47
48
|
"files": [
|
|
48
49
|
"index.js",
|
|
@@ -88,6 +89,7 @@
|
|
|
88
89
|
"inputs": [
|
|
89
90
|
"{projectRoot}/bin/package-build",
|
|
90
91
|
"{projectRoot}/bin/extension-dependencies-check",
|
|
92
|
+
"{projectRoot}/bin/extension-publish-check",
|
|
91
93
|
"{projectRoot}/index.ts",
|
|
92
94
|
"{projectRoot}/src/**/*.ts"
|
|
93
95
|
],
|
|
@@ -9,7 +9,7 @@ export const extensionDependencyVersionsFileEnvironmentVariable = "LENS_EXTENSIO
|
|
|
9
9
|
// Lens writes this file beside its extension-development README every time it loads. The name is
|
|
10
10
|
// repeated from `extensionDependencyVersionsFileName` of Lens's dynamic-features contracts, which a
|
|
11
11
|
// published build tool cannot depend on.
|
|
12
|
-
const defaultVersionsFilePath = () =>
|
|
12
|
+
export const defaultVersionsFilePath = () =>
|
|
13
13
|
path.join(
|
|
14
14
|
homedir(),
|
|
15
15
|
".k8slens",
|
|
@@ -29,6 +29,8 @@ export interface CheckExtensionDependenciesParams {
|
|
|
29
29
|
readonly versionsFilePath: string;
|
|
30
30
|
readonly fix: boolean;
|
|
31
31
|
readonly log: (line: string) => void;
|
|
32
|
+
/** Fails, instead of passing, when Lens has written no versions to check against: what publishing needs. */
|
|
33
|
+
readonly requireVersions?: boolean;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
36
|
interface Mismatch {
|
|
@@ -77,16 +79,25 @@ const indentationOf = (json: string) => /^([ \t]+)"/m.exec(json)?.[1] ?? 2;
|
|
|
77
79
|
* fails the check; one in `devDependencies` only warns, as Lens never reads those. With `fix`,
|
|
78
80
|
* rewrites every such range, in all three, to the caret range of the version Lens serves.
|
|
79
81
|
*
|
|
80
|
-
* @returns the exit code: 0 when Lens would load the extension, or when there is nothing to check against.
|
|
82
|
+
* @returns the exit code: 0 when Lens would load the extension, or when there is nothing to check against and `requireVersions` is not set.
|
|
81
83
|
*/
|
|
82
84
|
export const checkExtensionDependencies = ({
|
|
83
85
|
packageJsonPath,
|
|
84
86
|
versionsFilePath,
|
|
85
87
|
fix,
|
|
86
88
|
log,
|
|
89
|
+
requireVersions = false,
|
|
87
90
|
}: CheckExtensionDependenciesParams): number => {
|
|
88
91
|
const versions = readVersions(versionsFilePath);
|
|
89
92
|
|
|
93
|
+
if (!versions && requireVersions) {
|
|
94
|
+
log(
|
|
95
|
+
`Cannot check the dependency ranges against Lens: no "${versionsFilePath}", which Lens writes when it runs on this machine. Start Lens once, or name the versions of another Lens in ${extensionDependencyVersionsFileEnvironmentVariable}.`,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
if (!versions) {
|
|
91
102
|
log(
|
|
92
103
|
`Skipped checking dependency ranges against Lens: no "${versionsFilePath}", which Lens writes when it runs on this machine.`,
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { checkExtensionPublishing, extensionPublishCheck } from "./extension-publish-check";
|
|
5
|
+
|
|
6
|
+
describe("checking an extension is fit to publish, before npm publishes it", () => {
|
|
7
|
+
let directory: string;
|
|
8
|
+
let packageJsonPath: string;
|
|
9
|
+
let versionsFilePath: string;
|
|
10
|
+
let packedFiles: string[];
|
|
11
|
+
let logged: string[];
|
|
12
|
+
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
directory = mkdtempSync(path.join(tmpdir(), "extension-publish-check-"));
|
|
15
|
+
packageJsonPath = path.join(directory, "package.json");
|
|
16
|
+
versionsFilePath = path.join(directory, "extension-dependency-versions.json");
|
|
17
|
+
packedFiles = ["package.json", "dist/index.js"];
|
|
18
|
+
logged = [];
|
|
19
|
+
|
|
20
|
+
givenLensServes({ "some-package": "1.2.0" });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
rmSync(directory, { recursive: true, force: true });
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("given an extension fit to publish, passes", () => {
|
|
28
|
+
givenPackageJson(fitToPublish);
|
|
29
|
+
|
|
30
|
+
expect(check()).toBe(0);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe("given npm would pack the extension without its built entrypoint, when checked", () => {
|
|
34
|
+
let exitCode: number;
|
|
35
|
+
|
|
36
|
+
beforeEach(() => {
|
|
37
|
+
givenPackageJson(fitToPublish);
|
|
38
|
+
packedFiles = ["package.json", "src/index.ts"];
|
|
39
|
+
|
|
40
|
+
exitCode = check();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("fails", () => {
|
|
44
|
+
expect(exitCode).toBe(1);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("names the entrypoint, and how to have npm pack it", () => {
|
|
48
|
+
expect(logged).toContain(
|
|
49
|
+
'npm would publish the extension without its entrypoint "./dist/index.js", and Lens could not install it. Build first, and list the built directory in "files" of package.json, such as "files": ["dist"].',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe("given the entrypoint is named in exports, finds it packed by that name", () => {
|
|
55
|
+
it.each([
|
|
56
|
+
["as the exports themselves", { exports: "./dist/index.js" }],
|
|
57
|
+
['as the "." of exports', { exports: { ".": "./dist/index.js" } }],
|
|
58
|
+
['as the default condition of "."', { exports: { ".": { default: "./dist/index.js" } } }],
|
|
59
|
+
['as the require condition of "."', { exports: { ".": { require: "./dist/index.js" } } }],
|
|
60
|
+
])("%s", (_, entrypoint) => {
|
|
61
|
+
givenPackageJson({ ...fitToPublish, main: "./irrelevant-main.js", ...entrypoint });
|
|
62
|
+
|
|
63
|
+
expect(check()).toBe(0);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('given exports without "." of their own, finds the entrypoint named in main packed', () => {
|
|
68
|
+
givenPackageJson({ ...fitToPublish, exports: { "./package.json": "./package.json" } });
|
|
69
|
+
|
|
70
|
+
expect(check()).toBe(0);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("given npm lists the packed files with Windows separators, finds the entrypoint packed", () => {
|
|
74
|
+
givenPackageJson(fitToPublish);
|
|
75
|
+
packedFiles = ["package.json", "dist\\index.js"];
|
|
76
|
+
|
|
77
|
+
expect(check()).toBe(0);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("given package.json names no entrypoint, when checked", () => {
|
|
81
|
+
let exitCode: number;
|
|
82
|
+
|
|
83
|
+
beforeEach(() => {
|
|
84
|
+
givenPackageJson({ ...fitToPublish, main: undefined });
|
|
85
|
+
|
|
86
|
+
exitCode = check();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("fails", () => {
|
|
90
|
+
expect(exitCode).toBe(1);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("says what to name", () => {
|
|
94
|
+
expect(logged).toContain(
|
|
95
|
+
'package.json names no entrypoint: set "main" to the built file Lens loads, such as "./dist/index.js".',
|
|
96
|
+
);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("given main is not a path, says the extension names no entrypoint", () => {
|
|
101
|
+
givenPackageJson({ ...fitToPublish, main: 42 });
|
|
102
|
+
|
|
103
|
+
check();
|
|
104
|
+
|
|
105
|
+
expect(logged).toContain(
|
|
106
|
+
'package.json names no entrypoint: set "main" to the built file Lens loads, such as "./dist/index.js".',
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe("given a range Lens would refuse, when checked", () => {
|
|
111
|
+
let exitCode: number;
|
|
112
|
+
|
|
113
|
+
beforeEach(() => {
|
|
114
|
+
givenPackageJson({ ...fitToPublish, dependencies: { "some-package": "^1.2.1" } });
|
|
115
|
+
|
|
116
|
+
exitCode = check();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("fails", () => {
|
|
120
|
+
expect(exitCode).toBe(1);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("names the range and the one to declare instead", () => {
|
|
124
|
+
expect(logged).toContainEqual(
|
|
125
|
+
expect.stringContaining(
|
|
126
|
+
' in dependencies, "some-package": "^1.2.1" does not include Lens\'s 1.2.0, declare "^1.2.0"',
|
|
127
|
+
),
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
describe("given Lens on this machine has written no versions, when checked", () => {
|
|
133
|
+
let exitCode: number;
|
|
134
|
+
|
|
135
|
+
beforeEach(() => {
|
|
136
|
+
givenPackageJson(fitToPublish);
|
|
137
|
+
rmSync(versionsFilePath);
|
|
138
|
+
|
|
139
|
+
exitCode = check();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("fails, as publishing unchecked ranges is how an extension no Lens loads reaches users", () => {
|
|
143
|
+
expect(exitCode).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("says how to give it versions to check against", () => {
|
|
147
|
+
expect(logged).toContain(
|
|
148
|
+
`Cannot check the dependency ranges against Lens: no "${versionsFilePath}", which Lens writes when it runs on this machine. Start Lens once, or name the versions of another Lens in LENS_EXTENSION_DEPENDENCY_VERSIONS_FILE.`,
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
describe("given a prerelease version", () => {
|
|
154
|
+
beforeEach(() => {
|
|
155
|
+
givenPackageJson({ ...fitToPublish, version: "0.0.0-stage" });
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe("when checked for publishing as the latest version", () => {
|
|
159
|
+
let exitCode: number;
|
|
160
|
+
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
exitCode = check();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it("fails", () => {
|
|
166
|
+
expect(exitCode).toBe(1);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("says to publish it under a tag of its own, or as a release", () => {
|
|
170
|
+
expect(logged).toContain(
|
|
171
|
+
'0.0.0-stage is a prerelease, which would become the version users see and install by default. Publish it with "--tag next", or raise "version" in package.json to a release.',
|
|
172
|
+
);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("when checked for publishing explicitly as latest, fails", () => {
|
|
177
|
+
expect(check({ distTag: "latest" })).toBe(1);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it("when checked for publishing under a tag of its own, passes", () => {
|
|
181
|
+
expect(check({ distTag: "next" })).toBe(0);
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("given every kind of problem at once, says each of them, then that it will not publish", () => {
|
|
186
|
+
givenPackageJson({ ...fitToPublish, version: "0.0.0-stage", dependencies: { "some-package": "^2.0.0" } });
|
|
187
|
+
packedFiles = ["package.json"];
|
|
188
|
+
|
|
189
|
+
check();
|
|
190
|
+
|
|
191
|
+
expect(logged).toEqual([
|
|
192
|
+
[
|
|
193
|
+
`Lens would refuse to load this extension. These ranges in package.json do not include the version Lens serves (read from "${versionsFilePath}"):`,
|
|
194
|
+
' in dependencies, "some-package": "^2.0.0" does not include Lens\'s 1.2.0, declare "^1.2.0"',
|
|
195
|
+
'Run "npx lens-extension-dependencies-check --fix" to rewrite them, then "npm install".',
|
|
196
|
+
].join("\n"),
|
|
197
|
+
'npm would publish the extension without its entrypoint "./dist/index.js", and Lens could not install it. Build first, and list the built directory in "files" of package.json, such as "files": ["dist"].',
|
|
198
|
+
'0.0.0-stage is a prerelease, which would become the version users see and install by default. Publish it with "--tag next", or raise "version" in package.json to a release.',
|
|
199
|
+
"Not publishing: fix the above, and publish again.",
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe("as the command npm runs before publishing", () => {
|
|
204
|
+
let commandsRun: { command: string; directory: string }[];
|
|
205
|
+
let npmPrints: string;
|
|
206
|
+
|
|
207
|
+
beforeEach(() => {
|
|
208
|
+
commandsRun = [];
|
|
209
|
+
npmPrints = JSON.stringify([{ files: [{ path: "package.json" }, { path: "dist/index.js" }] }]);
|
|
210
|
+
givenPackageJson(fitToPublish);
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const runCommand = ({ environment = {} }: { environment?: Record<string, string> } = {}) =>
|
|
214
|
+
extensionPublishCheck({
|
|
215
|
+
directory,
|
|
216
|
+
environment: { LENS_EXTENSION_DEPENDENCY_VERSIONS_FILE: versionsFilePath, ...environment },
|
|
217
|
+
runCommand: (command, inDirectory) => {
|
|
218
|
+
commandsRun.push({ command, directory: inDirectory });
|
|
219
|
+
|
|
220
|
+
return npmPrints;
|
|
221
|
+
},
|
|
222
|
+
log: (line) => logged.push(line),
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("asks npm, in the extension's directory and running none of its scripts, what it would pack", () => {
|
|
226
|
+
runCommand();
|
|
227
|
+
|
|
228
|
+
expect(commandsRun).toEqual([{ command: "npm pack --dry-run --json --ignore-scripts", directory }]);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("reads what npm would pack past whatever npm prints before it", () => {
|
|
232
|
+
npmPrints = `npm warn some-warning\n${npmPrints}`;
|
|
233
|
+
|
|
234
|
+
expect(runCommand()).toBe(0);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("given npm prints no listing, finds nothing packed", () => {
|
|
238
|
+
npmPrints = "";
|
|
239
|
+
|
|
240
|
+
runCommand();
|
|
241
|
+
|
|
242
|
+
expect(logged).toContainEqual(expect.stringContaining("without its entrypoint"));
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("checks the ranges against the versions the environment names", () => {
|
|
246
|
+
writeFileSync(path.join(directory, "other-lens-versions.json"), JSON.stringify({ "some-package": "9.0.0" }));
|
|
247
|
+
givenPackageJson({ ...fitToPublish, dependencies: { "some-package": "^1.2.0" } });
|
|
248
|
+
|
|
249
|
+
expect(
|
|
250
|
+
runCommand({
|
|
251
|
+
environment: { LENS_EXTENSION_DEPENDENCY_VERSIONS_FILE: path.join(directory, "other-lens-versions.json") },
|
|
252
|
+
}),
|
|
253
|
+
).toBe(1);
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("given npm publishes a prerelease under a tag of its own, passes", () => {
|
|
257
|
+
givenPackageJson({ ...fitToPublish, version: "0.0.0-stage" });
|
|
258
|
+
|
|
259
|
+
expect(runCommand({ environment: { npm_config_tag: "next" } })).toBe(0);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
const fitToPublish = {
|
|
264
|
+
version: "0.1.0",
|
|
265
|
+
main: "./dist/index.js",
|
|
266
|
+
dependencies: { "some-package": "^1.2.0" },
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
function check({ distTag }: { distTag?: string } = {}) {
|
|
270
|
+
return checkExtensionPublishing({
|
|
271
|
+
packageJsonPath,
|
|
272
|
+
versionsFilePath,
|
|
273
|
+
packedFiles: () => packedFiles,
|
|
274
|
+
distTag,
|
|
275
|
+
log: (line) => logged.push(line),
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function givenPackageJson(packageJson: object) {
|
|
280
|
+
writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function givenLensServes(versions: Record<string, string>) {
|
|
284
|
+
writeFileSync(versionsFilePath, JSON.stringify(versions));
|
|
285
|
+
}
|
|
286
|
+
});
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import semver from "semver";
|
|
5
|
+
import {
|
|
6
|
+
checkExtensionDependencies,
|
|
7
|
+
defaultVersionsFilePath,
|
|
8
|
+
extensionDependencyVersionsFileEnvironmentVariable,
|
|
9
|
+
} from "./extension-dependencies-check";
|
|
10
|
+
|
|
11
|
+
/** The dist-tag a prerelease is published under, so it never becomes the version users install by default. */
|
|
12
|
+
export const prereleaseDistTag = "next";
|
|
13
|
+
|
|
14
|
+
export interface CheckExtensionPublishingParams {
|
|
15
|
+
readonly packageJsonPath: string;
|
|
16
|
+
readonly versionsFilePath: string;
|
|
17
|
+
/** The paths npm would pack, relative to the extension's directory, as `npm pack --dry-run` lists them. */
|
|
18
|
+
readonly packedFiles: () => readonly string[];
|
|
19
|
+
/** The dist-tag npm is publishing under, when one was given. */
|
|
20
|
+
readonly distTag: string | undefined;
|
|
21
|
+
readonly log: (line: string) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// The same reading of a manifest as Lens's own when it installs an extension, so what is checked
|
|
25
|
+
// here is what Lens will load. Lens's own Publish repeats it, as neither can depend on the other.
|
|
26
|
+
const relativeEntrypointOf = (packageJson: Record<string, unknown>): string | undefined => {
|
|
27
|
+
const { exports, main } = packageJson;
|
|
28
|
+
|
|
29
|
+
if (exports === undefined || exports === null) {
|
|
30
|
+
return typeof main === "string" ? main : undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (typeof exports === "string") {
|
|
34
|
+
return exports;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const defaultInExports = (exports as Record<string, unknown>)["."];
|
|
38
|
+
|
|
39
|
+
if (typeof defaultInExports === "string") {
|
|
40
|
+
return defaultInExports;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (typeof defaultInExports === "object" && defaultInExports !== null) {
|
|
44
|
+
const conditions = defaultInExports as Record<string, unknown>;
|
|
45
|
+
const conditional = conditions.default ?? conditions.require;
|
|
46
|
+
|
|
47
|
+
if (typeof conditional === "string") {
|
|
48
|
+
return conditional;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return typeof main === "string" ? main : undefined;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// The same reading of a path as Lens's own Publish, which checks what npm would pack too: "./dist\\index.js" is "dist/index.js".
|
|
56
|
+
const normalized = (relativePath: string) =>
|
|
57
|
+
relativePath
|
|
58
|
+
.replaceAll("\\", "/")
|
|
59
|
+
.split("/")
|
|
60
|
+
.filter((segment) => segment !== "" && segment !== ".")
|
|
61
|
+
.join("/");
|
|
62
|
+
|
|
63
|
+
const checkEntrypointIsPacked = (packageJson: Record<string, unknown>, packedFiles: () => readonly string[]) => {
|
|
64
|
+
const entrypoint = relativeEntrypointOf(packageJson);
|
|
65
|
+
|
|
66
|
+
if (entrypoint === undefined) {
|
|
67
|
+
return 'package.json names no entrypoint: set "main" to the built file Lens loads, such as "./dist/index.js".';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!packedFiles().map(normalized).includes(normalized(entrypoint))) {
|
|
71
|
+
return `npm would publish the extension without its entrypoint "${entrypoint}", and Lens could not install it. Build first, and list the built directory in "files" of package.json, such as "files": ["dist"].`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return undefined;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const checkPrereleaseIsTagged = (version: unknown, distTag: string | undefined) => {
|
|
78
|
+
const isPrerelease = typeof version === "string" && (semver.prerelease(version) ?? []).length > 0;
|
|
79
|
+
|
|
80
|
+
if (!isPrerelease || (distTag !== undefined && distTag !== "latest")) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return `${version} is a prerelease, which would become the version users see and install by default. Publish it with "--tag ${prereleaseDistTag}", or raise "version" in package.json to a release.`;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Checks an extension is fit to publish, before npm publishes it: that what npm packs has the
|
|
89
|
+
* entrypoint Lens loads, that Lens would accept every range the extension declares for a package
|
|
90
|
+
* Lens serves, and that a prerelease is not published as the latest version. Fails when Lens has
|
|
91
|
+
* written no versions to check the ranges against, as publishing unchecked is how an extension
|
|
92
|
+
* reaches users that no Lens loads.
|
|
93
|
+
*
|
|
94
|
+
* @returns the exit code: 0 when the extension is fit to publish.
|
|
95
|
+
*/
|
|
96
|
+
export const checkExtensionPublishing = ({
|
|
97
|
+
packageJsonPath,
|
|
98
|
+
versionsFilePath,
|
|
99
|
+
packedFiles,
|
|
100
|
+
distTag,
|
|
101
|
+
log,
|
|
102
|
+
}: CheckExtensionPublishingParams): number => {
|
|
103
|
+
const packageJson: Record<string, unknown> = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
|
|
104
|
+
|
|
105
|
+
const problems = [
|
|
106
|
+
checkEntrypointIsPacked(packageJson, packedFiles),
|
|
107
|
+
checkPrereleaseIsTagged(packageJson.version, distTag),
|
|
108
|
+
].filter((problem) => problem !== undefined);
|
|
109
|
+
|
|
110
|
+
const dependenciesExitCode = checkExtensionDependencies({
|
|
111
|
+
packageJsonPath,
|
|
112
|
+
versionsFilePath,
|
|
113
|
+
fix: false,
|
|
114
|
+
log,
|
|
115
|
+
requireVersions: true,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
for (const problem of problems) {
|
|
119
|
+
log(problem);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (problems.length > 0 || dependenciesExitCode !== 0) {
|
|
123
|
+
log("Not publishing: fix the above, and publish again.");
|
|
124
|
+
|
|
125
|
+
return 1;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return 0;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* The paths `npm pack --dry-run --json` says it would pack, from what it printed: one entry per
|
|
133
|
+
* package packed, listing its files, after whatever lines npm writes before it. None when npm
|
|
134
|
+
* printed no listing.
|
|
135
|
+
*/
|
|
136
|
+
export const packedFilesOf = (output: string): string[] => {
|
|
137
|
+
const listingStart = output.indexOf("[");
|
|
138
|
+
|
|
139
|
+
if (listingStart === -1) {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const [packed] = JSON.parse(output.slice(listingStart)) as { files?: { path: string }[] }[];
|
|
144
|
+
|
|
145
|
+
return packed?.files?.map((file) => file.path) ?? [];
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export interface ExtensionPublishCheckParams {
|
|
149
|
+
/** The extension's directory, where npm runs its scripts. */
|
|
150
|
+
readonly directory: string;
|
|
151
|
+
/** The environment npm runs the script in. */
|
|
152
|
+
readonly environment: Readonly<Record<string, string | undefined>>;
|
|
153
|
+
/** Runs a command in a directory, giving back what it printed. */
|
|
154
|
+
readonly runCommand: (command: string, directory: string) => string;
|
|
155
|
+
readonly log: (line: string) => void;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* The `lens-extension-publish-check` command, run in the extension's directory, as its
|
|
160
|
+
* `prepublishOnly` script: npm runs it before every publish, and gives up publishing when it fails.
|
|
161
|
+
*/
|
|
162
|
+
export const extensionPublishCheck = (
|
|
163
|
+
{ directory, environment, runCommand, log }: ExtensionPublishCheckParams = {
|
|
164
|
+
directory: process.cwd(),
|
|
165
|
+
environment: process.env,
|
|
166
|
+
runCommand: (command, cwd) => execSync(command, { cwd, encoding: "utf-8" }),
|
|
167
|
+
log: (line) => console.log(line),
|
|
168
|
+
},
|
|
169
|
+
): number =>
|
|
170
|
+
checkExtensionPublishing({
|
|
171
|
+
packageJsonPath: path.resolve(directory, "package.json"),
|
|
172
|
+
versionsFilePath: environment[extensionDependencyVersionsFileEnvironmentVariable] || defaultVersionsFilePath(),
|
|
173
|
+
packedFiles: () => packedFilesOf(runCommand("npm pack --dry-run --json --ignore-scripts", directory)),
|
|
174
|
+
// What npm tells the scripts it runs of `npm publish --tag <tag>`.
|
|
175
|
+
distTag: environment.npm_config_tag || undefined,
|
|
176
|
+
log,
|
|
177
|
+
});
|