@asrunjs/asrun 0.6.0 → 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/dist/scripts/doctor.js +7 -2
- package/dist/scripts/init.d.ts +2 -1
- package/dist/scripts/init.js +44 -11
- package/docs/install.md +5 -0
- package/docs/troubleshooting.md +6 -0
- package/package.json +1 -1
- package/templates/package.json +1 -0
package/dist/scripts/doctor.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
package/dist/scripts/init.d.ts
CHANGED
|
@@ -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 `{{
|
|
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
|
package/dist/scripts/init.js
CHANGED
|
@@ -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
|
-
/**
|
|
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
|
-
/**
|
|
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(
|
|
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
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
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;
|
package/docs/troubleshooting.md
CHANGED
|
@@ -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