demo-this-pr 0.1.1 → 1.0.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/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <img src="https://cdn.jsdelivr.net/npm/demo-this-pr@0.1.1/assets/demo-this-pr-logo.svg" alt="DEMO THIS PR animated wordmark" width="920">
2
+ <img src="https://cdn.jsdelivr.net/npm/demo-this-pr@1.0.0/assets/demo-this-pr-logo.svg" alt="DEMO THIS PR animated wordmark" width="920">
3
3
  </p>
4
4
 
5
5
  # demo-this-pr
@@ -70,7 +70,7 @@ For CI or agent runners, prefer a pinned package version and a deterministic
70
70
  artifact directory:
71
71
 
72
72
  ```bash
73
- npx demo-this-pr@0.1.1 ci
73
+ npx demo-this-pr@1.0.0 ci
74
74
  ```
75
75
 
76
76
  The command exits non-zero when Git context or a reachable local app is missing.
@@ -207,13 +207,19 @@ Generate in CI without a visible browser or report viewer:
207
207
  demo-this-pr ci
208
208
  ```
209
209
 
210
+ Delete all generated run artifacts for the current repository:
211
+
212
+ ```bash
213
+ demo-this-pr runs --delete
214
+ ```
215
+
210
216
  Jenkins example:
211
217
 
212
218
  ```groovy
213
219
  stage('PR demo') {
214
220
  steps {
215
221
  sh 'npm ci'
216
- sh 'npx demo-this-pr@0.1.1 ci'
222
+ sh 'npx demo-this-pr@1.0.0 ci'
217
223
  }
218
224
  post {
219
225
  always {
package/dist/cli.js CHANGED
@@ -1,17 +1,19 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
+ import { readFileSync } from "node:fs";
3
4
  import { resolve } from "node:path";
4
5
  import pc from "picocolors";
5
6
  import { resolveAutoDemo } from "./auto.js";
6
7
  import { createPrKit } from "./prKit.js";
7
8
  import { parseDemoStep } from "./demoPlan.js";
8
- import { defaultRunOutputDir } from "./runs.js";
9
+ import { defaultRunOutputDir, deleteRuns } from "./runs.js";
9
10
  import { openReportViewer } from "./viewer.js";
10
11
  const program = new Command();
12
+ const packageVersion = readPackageVersion();
11
13
  program
12
14
  .name("demo-this-pr")
13
15
  .description("Generate reviewable PR demos from current unpushed changes.")
14
- .version("0.1.0")
16
+ .version(packageVersion)
15
17
  .argument("[repo]", "repository path; defaults to the current working directory")
16
18
  .option("--cwd <path>", "repository path; useful when running from outside the target repo")
17
19
  .option("--headless", "run Chromium without a visible window", false)
@@ -20,7 +22,7 @@ program
20
22
  .action(async (repo, options) => {
21
23
  let cleanup;
22
24
  try {
23
- const targetCwd = resolve(options.cwd ?? repo ?? process.cwd());
25
+ const targetCwd = resolveTargetCwd(repo, options.cwd);
24
26
  const autoDemo = await resolveAutoDemo(targetCwd, {
25
27
  headless: options.headless ?? false,
26
28
  output: options.output,
@@ -48,7 +50,7 @@ program
48
50
  .action(async (repo, options) => {
49
51
  let cleanup;
50
52
  try {
51
- const targetCwd = resolve(options.cwd ?? repo ?? process.cwd());
53
+ const targetCwd = resolveTargetCwd(repo, options.cwd);
52
54
  const autoDemo = await resolveAutoDemo(targetCwd, {
53
55
  headless: !(options.headed ?? false),
54
56
  output: options.output,
@@ -66,6 +68,31 @@ program
66
68
  await cleanup?.();
67
69
  }
68
70
  });
71
+ program
72
+ .command("runs")
73
+ .description("Manage generated demo-this-pr run artifacts")
74
+ .argument("[repo]", "repository path; defaults to the current working directory")
75
+ .option("--cwd <path>", "repository path; useful when running from outside the target repo")
76
+ .option("--delete", "delete all run directories under .demo-this-pr/runs", false)
77
+ .action(async (repo, options) => {
78
+ if (!options.delete) {
79
+ process.stderr.write(`${pc.red("demo-this-pr:")} runs needs --delete\n`);
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+ try {
84
+ const targetCwd = resolveTargetCwd(repo, options.cwd);
85
+ const result = await deleteRuns(targetCwd);
86
+ process.stdout.write(`${pc.green("Runs deleted")}\n`);
87
+ process.stdout.write(`Directory: ${result.runsDir}\n`);
88
+ process.stdout.write(`Deleted: ${result.deletedCount}\n`);
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ process.stderr.write(`${pc.red("demo-this-pr:")} ${message}\n`);
93
+ process.exitCode = 1;
94
+ }
95
+ });
69
96
  program
70
97
  .command("demo")
71
98
  .description("Launch a local Chromium demo for the current changes and write the PR kit")
@@ -199,3 +226,11 @@ function printResult(result, label, notes) {
199
226
  function collect(value, previous) {
200
227
  return [...previous, value];
201
228
  }
229
+ function resolveTargetCwd(repo, commandCwd) {
230
+ const rootOptions = program.opts();
231
+ return resolve(commandCwd ?? rootOptions.cwd ?? repo ?? process.cwd());
232
+ }
233
+ function readPackageVersion() {
234
+ const packageJson = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
235
+ return packageJson.version ?? "0.0.0";
236
+ }
package/dist/runs.d.ts CHANGED
@@ -1 +1,6 @@
1
+ export interface DeleteRunsResult {
2
+ runsDir: string;
3
+ deletedCount: number;
4
+ }
1
5
  export declare function defaultRunOutputDir(label: string | undefined): string;
6
+ export declare function deleteRuns(cwd: string): Promise<DeleteRunsResult>;
package/dist/runs.js CHANGED
@@ -1,6 +1,35 @@
1
+ import { lstat, readdir, rm } from "node:fs/promises";
2
+ import { join, resolve } from "node:path";
1
3
  export function defaultRunOutputDir(label) {
2
4
  return `.demo-this-pr/runs/${timestamp()}-${slug(normalizeLabel(label))}`;
3
5
  }
6
+ export async function deleteRuns(cwd) {
7
+ const runsDir = resolve(cwd, ".demo-this-pr", "runs");
8
+ try {
9
+ const stats = await lstat(runsDir);
10
+ if (stats.isSymbolicLink()) {
11
+ throw new Error(`Refusing to delete symlinked runs directory: ${runsDir}`);
12
+ }
13
+ if (!stats.isDirectory()) {
14
+ throw new Error(`Runs path exists but is not a directory: ${runsDir}`);
15
+ }
16
+ }
17
+ catch (error) {
18
+ if (isNotFoundError(error)) {
19
+ return { runsDir, deletedCount: 0 };
20
+ }
21
+ throw error;
22
+ }
23
+ const entries = await readdir(runsDir);
24
+ await Promise.all(entries.map((entry) => rm(join(runsDir, entry), { recursive: true, force: true })));
25
+ return {
26
+ runsDir,
27
+ deletedCount: entries.length
28
+ };
29
+ }
30
+ function isNotFoundError(error) {
31
+ return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
32
+ }
4
33
  function timestamp() {
5
34
  const now = new Date();
6
35
  const pad = (value, size = 2) => String(value).padStart(size, "0");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demo-this-pr",
3
- "version": "0.1.1",
3
+ "version": "1.0.0",
4
4
  "description": "Record local PR demos with isolated Playwright Chromium, MP4 evidence, test output, and review-ready reports.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",