@asrunjs/asrun 0.5.3 → 0.6.1

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/LICENSE CHANGED
@@ -1,21 +1,9 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Zoro Chen
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Copyright (c) 2026 Zoro Chen. All rights reserved.
2
+
3
+ This software is proprietary. No permission is granted to use, copy, modify, merge, publish, distribute, sublicense,
4
+ or sell copies of the software, in whole or in part, without prior written permission from the copyright holder.
5
+
6
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
7
+ WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHOR BE
8
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
9
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -218,4 +218,4 @@ Framework layout, the two agent CLIs, the UI theme and copy rules, and where doc
218
218
 
219
219
  ## License
220
220
 
221
- MIT. Created by Zoro Chen.
221
+ Proprietary. Copyright (c) 2026 Zoro Chen. All rights reserved. AsRun is not open source; see [LICENSE](./LICENSE).
@@ -3,7 +3,7 @@ import path from 'node:path';
3
3
  import { loadProfiles, listEnvs, PROFILES_FILE } from '../lib/profiles.js';
4
4
  import { FRAMEWORK_ROOT, ROOT, SKILLS_DIR } from '../lib/paths.js';
5
5
  import { spawnTarget } from '../lib/command.js';
6
- import { GENERATED, frameworkPath, frameworkVersion, renderTemplate } from './init.js';
6
+ import { GENERATED, frameworkPath, frameworkVersion, PACKAGE, renderTemplate } from './init.js';
7
7
  import { builtFile, COPIED_SCRIPTS } from '../lib/build-artifacts.js';
8
8
  /**
9
9
  * Checks that a workspace is complete and consistent with the framework it points at. It never writes anything.
@@ -83,12 +83,17 @@ export async function main(args) {
83
83
  facts.push(`workspace: ${ROOT}`);
84
84
  const actualPath = frameworkPath(ROOT);
85
85
  facts.push(`framework: ${path.resolve(ROOT, actualPath)}`);
86
+ if (!fs.existsSync(path.join(path.resolve(ROOT, actualPath), 'package.json'))) {
87
+ errors.push(`${PACKAGE} is not installed in this workspace: run "pnpm add -D ${PACKAGE}", then "pnpm asrun init". ` +
88
+ 'Every script that calls "asrun" fails until then.');
89
+ }
86
90
  // The manifest, written by init: it names where the workspace reaches the framework.
87
91
  const manifest = read(path.join(ROOT, '.asrun.json'));
88
92
  let recordedPath;
89
93
  const installedVersion = frameworkVersion();
90
94
  if (manifest === null) {
91
- warnings.push('No .asrun.json: run "pnpm asrun init" in this folder.');
95
+ errors.push('No .asrun.json: this workspace was never initialized, or "init" stopped before it finished (for example the ' +
96
+ 'pnpm add step failed). Run "pnpm asrun init" here.');
92
97
  }
93
98
  else {
94
99
  try {
@@ -3,11 +3,12 @@ export declare const GENERATED: {
3
3
  file: string;
4
4
  template: string;
5
5
  }[];
6
+ export declare const PACKAGE = "@asrunjs/asrun";
6
7
  /** How the workspace reaches the framework: `./node_modules/@asrunjs/asrun`, or a relative folder in the framework repo. */
7
8
  export declare function frameworkPath(root: string): string;
8
9
  /** True when this workspace has the package installed (or is the framework's own checkout). */
9
10
  export declare function isInstalled(root: string): boolean;
10
- /** Fills the `{{framework}}` and `{{playwright}}` placeholders of a template. */
11
+ /** Fills the `{{framework}}`, `{{playwright}}` and `{{asrun}}` placeholders of a template. */
11
12
  export declare function renderTemplate(name: string, root: string): string;
12
13
  /**
13
14
  * The Playwright CLI the MCP server runs. The workspace's own install is the normal place; a workspace that lives
@@ -14,7 +14,7 @@ const USAGE = `Usage: pnpm asrun init [--root <dir>] [--update] [--force <path>]
14
14
  --force <path> overwrite one file this tool writes, e.g. --force playwright.config.ts (may be repeated)
15
15
  --reset-rules allow --force on AGENTS.md, CLAUDE.md or .claude/skills/** (project-owned rules)
16
16
  --no-env do not write .env (ASRUN_ROOT)
17
- --install [spec] also add the dependency (default "asrun"; e.g. --install github:<owner>/<repo>#<tag>)`;
17
+ --install [spec] also add the dependency (default "@asrunjs/asrun"; e.g. --install github:<owner>/<repo>#<tag>)`;
18
18
  /** Files `init` owns: `--update` rewrites exactly these, and a project never edits them. */
19
19
  export const GENERATED = [
20
20
  { file: 'playwright.config.ts', template: 'playwright.config.ts' },
@@ -46,7 +46,7 @@ const ONCE = [
46
46
  const GITIGNORE_BLOCK = fs.readFileSync(path.join(TEMPLATES_DIR, 'gitignore'), 'utf8').trimEnd();
47
47
  const MARKER = '.asrun.json';
48
48
  const DOTENV_HEADER = '# asrun: the workspace these commands run against (relative to this file).';
49
- const PACKAGE = '@asrunjs/asrun';
49
+ export const PACKAGE = '@asrunjs/asrun';
50
50
  function readTemplate(name) {
51
51
  return fs.readFileSync(path.join(TEMPLATES_DIR, name), 'utf8');
52
52
  }
@@ -79,11 +79,18 @@ export function frameworkPath(root) {
79
79
  export function isInstalled(root) {
80
80
  return fs.existsSync(path.join(path.resolve(root, frameworkPath(root)), 'package.json'));
81
81
  }
82
- /** Fills the `{{framework}}` and `{{playwright}}` placeholders of a template. */
82
+ /** The range the workspace's `package.json` names for the framework: `^` plus the version that wrote the workspace. */
83
+ let asrunRange;
84
+ function asrunDependencyRange() {
85
+ asrunRange ??= `^${frameworkVersion().version}`;
86
+ return asrunRange;
87
+ }
88
+ /** Fills the `{{framework}}`, `{{playwright}}` and `{{asrun}}` placeholders of a template. */
83
89
  export function renderTemplate(name, root) {
84
90
  return readTemplate(name)
85
91
  .replaceAll('{{framework}}', frameworkPath(root))
86
- .replaceAll('{{playwright}}', playwrightBin(root));
92
+ .replaceAll('{{playwright}}', playwrightBin(root))
93
+ .replaceAll('{{asrun}}', asrunDependencyRange());
87
94
  }
88
95
  /**
89
96
  * The Playwright CLI the MCP server runs. The workspace's own install is the normal place; a workspace that lives
@@ -113,13 +120,21 @@ export function frameworkVersion() {
113
120
  }
114
121
  return { version, commit };
115
122
  }
116
- /** Merges the template's npm scripts and devDependencies into an existing package.json (never overwrites). */
123
+ /** True when any dependency block of the manifest already names `name`, whatever the range. */
124
+ function declaredIn(manifest, name) {
125
+ return [manifest.dependencies, manifest.devDependencies, manifest.optionalDependencies, manifest.peerDependencies].some((block) => block?.[name] !== undefined);
126
+ }
127
+ /**
128
+ * Merges the template's npm scripts and devDependencies into an existing package.json (never overwrites). The
129
+ * framework itself is one of those devDependencies, so a workspace whose `pnpm add` step failed still names it and
130
+ * `pnpm install` is enough to fix it.
131
+ */
117
132
  function mergePackageJson(root) {
118
133
  const file = path.join(root, 'package.json');
119
134
  if (!fs.existsSync(file))
120
135
  return [];
121
136
  const current = JSON.parse(fs.readFileSync(file, 'utf8'));
122
- const template = JSON.parse(readTemplate('package.json'));
137
+ const template = JSON.parse(renderTemplate('package.json', root));
123
138
  const added = [];
124
139
  current.scripts ??= {};
125
140
  for (const [name, command] of Object.entries(template.scripts)) {
@@ -130,10 +145,10 @@ function mergePackageJson(root) {
130
145
  }
131
146
  current.devDependencies ??= {};
132
147
  for (const [name, range] of Object.entries(template.devDependencies)) {
133
- if (current.devDependencies[name] === undefined) {
134
- current.devDependencies[name] = range;
135
- added.push(`devDependencies.${name}`);
136
- }
148
+ if (declaredIn(current, name))
149
+ continue;
150
+ current.devDependencies[name] = range;
151
+ added.push(`devDependencies.${name}`);
137
152
  }
138
153
  if (added.length > 0)
139
154
  fs.writeFileSync(file, JSON.stringify(current, null, 2) + '\n');
@@ -294,7 +309,25 @@ export async function main(args) {
294
309
  // "pnpm add -D @asrunjs/asrun" fails on pnpm 12 with ERR_PNPM_IGNORED_BUILDS.
295
310
  const installed = isInstalled(root);
296
311
  if (install && !installed) {
297
- if (spawnSync('pnpm', ['add', '-D', installSpec], { cwd: root, stdio: 'inherit', shell: process.platform === 'win32' }).status !== 0) {
312
+ const result = spawnSync('pnpm', ['add', '-D', installSpec], {
313
+ cwd: root,
314
+ stdio: 'inherit',
315
+ shell: process.platform === 'win32',
316
+ });
317
+ if (result.status !== 0) {
318
+ // `status` is null when pnpm never started (not on PATH, or killed): pnpm prints nothing then, so without this
319
+ // message the workspace would stay half initialized with no explanation.
320
+ const error = result.error;
321
+ const reason = error !== undefined
322
+ ? error.code === 'ENOENT'
323
+ ? 'pnpm was not found on PATH'
324
+ : error.message
325
+ : result.signal !== null
326
+ ? `pnpm was stopped by ${result.signal}`
327
+ : `pnpm add exited with code ${result.status}`;
328
+ console.error(`\n ! ${installSpec} was not installed: ${reason}`);
329
+ console.error(` Run "pnpm add -D ${installSpec}" in ${root}, then "pnpm asrun init" again.`);
330
+ console.error(' The files above are written, but nothing that calls "asrun" works until the dependency is there.');
298
331
  return 1;
299
332
  }
300
333
  }
package/docs/install.md CHANGED
@@ -26,6 +26,11 @@ writes `pnpm-workspace.yaml` with the `allowBuilds` entry (`esbuild` through `ts
26
26
  `pnpm add` exits with `ERR_PNPM_IGNORED_BUILDS`. An existing `pnpm-workspace.yaml` that lacks the entry gets a note
27
27
  instead: add the block by hand (see "Manual install").
28
28
 
29
+ `init` also records `@asrunjs/asrun` (pinned to its own version) in the workspace `package.json`, so `pnpm install`
30
+ installs the framework even without `--install`. When the install step itself fails — `pnpm` is not on `PATH`, the
31
+ registry is unreachable, or a build script is not approved — `init` names the reason and stops before writing
32
+ `.asrun.json`: fix the cause, run the `pnpm add -D @asrunjs/asrun` it prints, and run `pnpm asrun init` again.
33
+
29
34
  `pnpm go` then does the rest and is the command to remember:
30
35
 
31
36
  1. checks Node (22+) and ffmpeg, and warns when no AI CLI is installed;
@@ -95,6 +95,12 @@ project. Add `--reset-rules` to replace them with the template, and expect to lo
95
95
  `pnpm dlx @asrunjs/asrun init` first (it writes the `allowBuilds` entry), or add it to `pnpm-workspace.yaml` by hand;
96
96
  see [install.md](./install.md#manual-install).
97
97
 
98
+ **`init` stopped and `.asrun.json` is missing** — the workspace files are written, but the `pnpm add -D
99
+ @asrunjs/asrun` step failed, and `init` printed the reason (most often `pnpm` is not on `PATH`, or the registry
100
+ refused the install). Run the command it prints, then `pnpm asrun init` again. Until then `pnpm asrun` and every
101
+ script that calls `asrun` do not exist, and `pnpm asrun doctor` reports both the missing framework and the missing
102
+ marker.
103
+
98
104
  **The AI did something you did not expect** — exploring and generating only read pages and edit the case file; healing
99
105
  edits the spec and runs it under the same write gate as any run (staging writes are allowed, any other environment
100
106
  needs a person's confirmation). None of them writes product data on its own, and none updates a baseline. If a step
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@asrunjs/asrun",
3
- "version": "0.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Multi-profile end-to-end test suite: plain-language cases, AI-written Playwright specs, evidence for every run.",
5
- "license": "MIT",
5
+ "license": "UNLICENSED",
6
6
  "author": "Zoro Chen",
7
7
  "repository": {
8
8
  "type": "git",
@@ -24,6 +24,7 @@
24
24
  "ui:dev": "asrun ui:dev"
25
25
  },
26
26
  "devDependencies": {
27
+ "@asrunjs/asrun": "{{asrun}}",
27
28
  "@playwright/test": "1.64.1",
28
29
  "@types/node": "^22.20.5",
29
30
  "typescript": "^7.0.2"