pkg-pr-new 0.0.8 → 0.0.10

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/index.ts CHANGED
@@ -6,21 +6,26 @@ import { createHash } from "node:crypto";
6
6
  import { hash } from "ohash";
7
7
  import fsSync from "fs";
8
8
  import fs from "fs/promises";
9
- import { Octokit } from "@octokit/action";
10
- import { getPackageManifest } from "query-registry";
11
- import { extractOwnerAndRepo, extractRepository } from "@pkg-pr-new/utils";
9
+ import { getPackageManifest, type PackageManifest } from "query-registry";
10
+ import type { Comment } from "@pkg-pr-new/utils";
11
+ import {
12
+ abbreviateCommitHash,
13
+ extractOwnerAndRepo,
14
+ extractRepository,
15
+ } from "@pkg-pr-new/utils";
12
16
  import fg from "fast-glob";
13
17
  import ignore from "ignore";
14
18
  import "./environments";
15
19
  import pkg from "./package.json" with { type: "json" };
16
20
  import { isBinaryFile } from "isbinaryfile";
17
21
  import { readPackageJSON, writePackageJSON } from "pkg-types";
22
+ import { createDefaultTemplate } from "./template";
18
23
 
19
24
  declare global {
20
25
  var API_URL: string;
21
26
  }
22
27
 
23
- const apiUrl = process.env.API_URL ?? API_URL
28
+ const apiUrl = process.env.API_URL ?? API_URL;
24
29
  const publishUrl = new URL("/publish", apiUrl);
25
30
 
26
31
  const main = defineCommand({
@@ -47,25 +52,32 @@ const main = defineCommand({
47
52
  description:
48
53
  "generate stackblitz templates out of directories in the current repo with the new built packages",
49
54
  },
55
+ comment: {
56
+ type: "string", // "off", "create", "update" (default)
57
+ description: `"off" for no comments (silent mode). "create" for comment on each publish. "update" for one comment across the pull request with edits on each publish (default)`,
58
+ default: "update",
59
+ },
50
60
  },
51
61
  run: async ({ args }) => {
52
62
  const paths = (args._.length ? args._ : ["."])
53
63
  .flatMap((p) => (fg.isDynamicPattern(p) ? fg.sync(p) : p))
54
- .map((p) => path.resolve(p));
64
+ .map((p) => path.resolve(p.trim()));
55
65
 
56
66
  const templates = (
57
67
  typeof args.template === "string"
58
68
  ? [args.template]
59
- : ([...(args.template ?? [])] as string[])
69
+ : ([...(args.template || [])] as string[])
60
70
  )
61
71
  .flatMap((p) => (fg.isDynamicPattern(p) ? fg.sync(p) : p))
62
- .map((p) => path.resolve(p));
72
+ .map((p) => path.resolve(p.trim()));
63
73
 
64
74
  const formData = new FormData();
65
75
 
66
76
  const isCompact = !!args.compact;
67
77
  const isPnpm = !!args.pnpm;
68
78
 
79
+ const comment: Comment = args.comment as Comment;
80
+
69
81
  if (!process.env.TEST && process.env.GITHUB_ACTIONS !== "true") {
70
82
  console.error(
71
83
  "Continuous Releases are only available in Github Actions.",
@@ -107,16 +119,23 @@ const main = defineCommand({
107
119
  }
108
120
 
109
121
  const { sha } = await checkResponse.json();
122
+ const abbreviatedSha = abbreviateCommitHash(sha);
110
123
 
111
124
  const deps: Map<string, string> = new Map();
112
125
 
113
126
  for (const p of paths) {
127
+ if (!(await hasPackageJson(p))) {
128
+ continue;
129
+ }
114
130
  const pJsonPath = path.resolve(p, "package.json");
115
131
  const pJson = await readPackageJSON(pJsonPath);
116
132
 
117
133
  if (!pJson.name) {
118
134
  throw new Error(`"name" field in ${pJsonPath} should be defined`);
119
135
  }
136
+ if (pJson.private) {
137
+ continue;
138
+ }
120
139
 
121
140
  if (isCompact) {
122
141
  await verifyCompactMode(pJson.name);
@@ -125,19 +144,31 @@ const main = defineCommand({
125
144
  deps.set(
126
145
  pJson.name,
127
146
  new URL(
128
- `/${owner}/${repo}/${pJson.name}@${sha}`,
147
+ `/${owner}/${repo}/${pJson.name}@${abbreviatedSha}`,
129
148
  apiUrl,
130
149
  ).href,
131
150
  );
132
151
  }
133
152
 
134
153
  for (const templateDir of templates) {
154
+ if (!(await hasPackageJson(templateDir))) {
155
+ console.log(
156
+ `skipping ${templateDir} because there's no package.json file`,
157
+ );
158
+ continue;
159
+ }
135
160
  const pJsonPath = path.resolve(templateDir, "package.json");
136
161
  const pJson = await readPackageJSON(pJsonPath);
137
162
 
138
163
  if (!pJson.name) {
139
164
  throw new Error(`"name" field in ${pJsonPath} should be defined`);
140
165
  }
166
+ if (pJson.private) {
167
+ console.log(
168
+ `skipping ${templateDir} because the package is private`,
169
+ );
170
+ continue;
171
+ }
141
172
 
142
173
  console.log("preparing template:", pJson.name);
143
174
 
@@ -174,16 +205,45 @@ const main = defineCommand({
174
205
  await restore();
175
206
  }
176
207
 
208
+ const noDefaultTemplate = args.template === false;
209
+
210
+ if (!templates.length && !noDefaultTemplate) {
211
+ const project = createDefaultTemplate(
212
+ Object.fromEntries(deps.entries()),
213
+ );
214
+
215
+ for (const filePath of Object.keys(project)) {
216
+ formData.append(
217
+ `template:default:${encodeURIComponent(filePath)}`,
218
+ project[filePath],
219
+ );
220
+ }
221
+ }
222
+
177
223
  const restoreMap = new Map<
178
224
  string,
179
225
  Awaited<ReturnType<typeof writeDeps>>
180
226
  >();
181
227
  for (const p of paths) {
228
+ if (!(await hasPackageJson(p))) {
229
+ continue;
230
+ }
231
+ const pJsonPath = path.resolve(p, "package.json");
232
+ const pJson = await readPackageJSON(pJsonPath);
233
+
234
+ if (pJson.private) {
235
+ continue;
236
+ }
237
+
182
238
  restoreMap.set(p, await writeDeps(p, deps));
183
239
  }
184
240
 
185
241
  const shasums: Record<string, string> = {};
186
242
  for (const p of paths) {
243
+ if (!(await hasPackageJson(p))) {
244
+ console.log(`skipping ${p} because there's no package.json file`);
245
+ continue;
246
+ }
187
247
  const pJsonPath = path.resolve(p, "package.json");
188
248
  try {
189
249
  const pJson = await readPackageJSON(pJsonPath);
@@ -193,6 +253,10 @@ const main = defineCommand({
193
253
  `"name" field in ${pJsonPath} should be defined`,
194
254
  );
195
255
  }
256
+ if (pJson.private) {
257
+ console.log(`skipping ${p} because the package is private`);
258
+ continue;
259
+ }
196
260
 
197
261
  const { filename, shasum } = await resolveTarball(
198
262
  isPnpm ? "pnpm" : "npm",
@@ -209,13 +273,14 @@ const main = defineCommand({
209
273
  });
210
274
  formData.append(`package:${pJson.name}`, blob, filename);
211
275
  } finally {
212
- await restoreMap.get(pJsonPath)?.();
276
+ await restoreMap.get(p)!();
213
277
  }
214
278
  }
215
279
 
216
280
  const res = await fetch(publishUrl, {
217
281
  method: "POST",
218
282
  headers: {
283
+ "sb-comment": comment,
219
284
  "sb-compact": `${isCompact}`,
220
285
  "sb-key": key,
221
286
  "sb-shasums": JSON.stringify(shasums),
@@ -256,30 +321,18 @@ runMain(main);
256
321
 
257
322
  // TODO: we'll add support for yarn if users hit issues with npm
258
323
  async function resolveTarball(pm: "npm" | "pnpm", p: string) {
259
- if (pm === "npm") {
260
- const { stdout } = await ezSpawn.async("npm pack --json", {
261
- stdio: "overlapped",
262
- cwd: p,
263
- });
264
-
265
- const { filename, shasum }: { filename: string; shasum: string } =
266
- JSON.parse(stdout)[0];
267
-
268
- return { filename, shasum };
269
- } else if (pm === "pnpm") {
270
- const { stdout } = await ezSpawn.async("pnpm pack", {
271
- stdio: "overlapped",
272
- cwd: p,
273
- });
274
- const filename = stdout.trim();
275
-
276
- const shasum = createHash("sha1")
277
- .update(await fs.readFile(path.resolve(p, filename)))
278
- .digest("hex");
279
-
280
- return { filename, shasum };
281
- }
282
- throw new Error("Could not resolve package manager");
324
+ const { stdout } = await ezSpawn.async(`${pm} pack`, {
325
+ stdio: "overlapped",
326
+ cwd: p,
327
+ });
328
+ const lines = stdout.split("\n").filter(Boolean);
329
+ const filename = lines[lines.length - 1].trim();
330
+
331
+ const shasum = createHash("sha1")
332
+ .update(await fs.readFile(path.resolve(p, filename)))
333
+ .digest("hex");
334
+
335
+ return { filename, shasum };
283
336
  }
284
337
 
285
338
  async function writeDeps(p: string, deps: Map<string, string>) {
@@ -311,23 +364,42 @@ function hijackDeps(
311
364
  }
312
365
 
313
366
  async function verifyCompactMode(packageName: string) {
314
- const error = new Error(
315
- `pkg-pr-new cannot resolve ${packageName} from npm. --compact flag depends on the package being available in npm.
316
- Make sure to have your package on npm first or configure the 'repository' field in your package.json properly.`,
317
- );
367
+ let manifest: PackageManifest;
368
+
318
369
  try {
319
- const manifest = await getPackageManifest(packageName);
370
+ manifest = await getPackageManifest(packageName);
371
+ } catch {
372
+ throw new Error(
373
+ `pkg-pr-new cannot resolve ${packageName} from npm. --compact flag depends on the package being available in npm.
374
+ Make sure to have your package on npm first.`,
375
+ );
376
+ }
320
377
 
321
- const repository = extractRepository(manifest);
322
- if (!repository) {
323
- throw error;
324
- }
378
+ const instruction = `Make sure to configure the 'repository' / 'repository.url' field in its package.json properly.
379
+ See https://docs.npmjs.com/cli/v10/configuring-npm/package-json#repository for details.`;
325
380
 
326
- const match = extractOwnerAndRepo(repository);
327
- if (!match) {
328
- throw error;
329
- }
381
+ const repository = extractRepository(manifest);
382
+ if (!repository) {
383
+ throw new Error(
384
+ `pkg-pr-new cannot extract the repository link from the ${packageName} manifest. --compact flag requires the link to be present.
385
+ ${instruction}`,
386
+ );
387
+ }
388
+
389
+ const match = extractOwnerAndRepo(repository);
390
+ if (!match) {
391
+ throw new Error(
392
+ `pkg-pr-new cannot extract the owner and repo names from the ${packageName} repository link: ${repository}. --compact flag requires these names.
393
+ ${instruction}`,
394
+ );
395
+ }
396
+ }
397
+
398
+ async function hasPackageJson(p: string) {
399
+ try {
400
+ await fs.access(path.resolve(p, "package.json"), fs.constants.F_OK);
401
+ return true;
330
402
  } catch {
331
- throw error;
403
+ return false;
332
404
  }
333
405
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pkg-pr-new",
3
- "version": "0.0.8",
3
+ "version": "0.0.10",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "ignore": "^5.3.1",
23
23
  "isbinaryfile": "^5.0.2",
24
24
  "pkg-types": "^1.1.1",
25
- "query-registry": "^3.0.0"
25
+ "query-registry": "^3.0.1"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@pkg-pr-new/utils": "workspace:^",
package/template.ts ADDED
@@ -0,0 +1,54 @@
1
+ export const createDefaultTemplate = (
2
+ dependencies: Record<string, string>,
3
+ ) => ({
4
+ "index.js": "",
5
+ "README.md": `
6
+ # Default Template
7
+
8
+ This is a template that leverages the experimental templates feature in the \`pkg.pr.new\` tool.
9
+
10
+ ## Overview
11
+
12
+ Templates are particularly useful for creating live, interactive examples of your packages, which can be very beneficial for both development and documentation purposes.
13
+
14
+ As a user, you can check the package.json file and see the new generated packages! You can just copy those and put them in your package.json or install them with your favorite package manager.
15
+
16
+ ${Object.values(dependencies)
17
+ .map(
18
+ (url) => `
19
+ \`\`\`sh
20
+ npm i ${url}
21
+ \`\`\`
22
+ `,
23
+ )
24
+ .join("")}
25
+
26
+ ## Usage
27
+
28
+ To use this feature as a maintainer, you can run the following command:
29
+
30
+ \`\`\`sh
31
+ npx pkg-pr-new publish ./packages/A --template ./examples/*
32
+ \`\`\`
33
+
34
+ ## Benefits
35
+
36
+ - Interactive Demos: Automatically create live demos that users can interact with directly in their browser.
37
+ - Enhanced Testing: Quickly spin up environments to test your package in different scenarios.
38
+ - Improved Sharing: Easily share working examples of your package with collaborators or users without needing them to set up their environment.
39
+ `,
40
+ "package.json": JSON.stringify(
41
+ {
42
+ name: "default",
43
+ version: "1.0.0",
44
+ description: "generated by pkg.pr.new",
45
+ main: "index.js",
46
+ dependencies,
47
+ keywords: [],
48
+ author: "pkg.pr.new",
49
+ license: "ISC",
50
+ },
51
+ null,
52
+ 2,
53
+ ),
54
+ });
Binary file