@haposoft/cafekit 0.11.8 → 0.11.9
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/lib/skill-deps.js +49 -21
- package/bin/phases/skills-setup.js +5 -5
- package/package.json +1 -1
package/bin/lib/skill-deps.js
CHANGED
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
|
|
14
14
|
const fs = require('fs');
|
|
15
15
|
const path = require('path');
|
|
16
|
-
const { spawnSync } = require('child_process');
|
|
16
|
+
const { spawnSync, spawn } = require('child_process');
|
|
17
17
|
|
|
18
18
|
const isWindows = process.platform === 'win32';
|
|
19
19
|
|
|
20
|
-
/** Run a command, return { ok, stdout, stderr, status }. Never throws. */
|
|
20
|
+
/** Run a command synchronously, return { ok, stdout, stderr, status }. Never throws. */
|
|
21
21
|
function run(cmd, args, opts = {}) {
|
|
22
22
|
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
23
23
|
return {
|
|
@@ -28,6 +28,31 @@ function run(cmd, args, opts = {}) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Run a command asynchronously — keeps the event loop alive so spinners can animate.
|
|
33
|
+
* Returns a Promise<{ ok, stdout, stderr, status }>. Never rejects.
|
|
34
|
+
*/
|
|
35
|
+
function runAsync(cmd, args, opts = {}) {
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
const child = spawn(cmd, args, { stdio: 'pipe', ...opts });
|
|
38
|
+
let stdout = '';
|
|
39
|
+
let stderr = '';
|
|
40
|
+
if (child.stdout) child.stdout.on('data', (d) => { stdout += d.toString(); });
|
|
41
|
+
if (child.stderr) child.stderr.on('data', (d) => { stderr += d.toString(); });
|
|
42
|
+
child.on('close', (code) => {
|
|
43
|
+
resolve({
|
|
44
|
+
ok: code === 0,
|
|
45
|
+
status: code,
|
|
46
|
+
stdout,
|
|
47
|
+
stderr
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
child.on('error', (err) => {
|
|
51
|
+
resolve({ ok: false, status: null, stdout, stderr: stderr || err.message });
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
/** Is a command available on PATH? */
|
|
32
57
|
function hasCmd(name) {
|
|
33
58
|
const probe = isWindows ? run('where', [name]) : run('which', [name]);
|
|
@@ -76,35 +101,37 @@ function pipInstall(skillsDir, requirementsPath, upgrade = false) {
|
|
|
76
101
|
return run(venvPython(skillsDir), args).ok;
|
|
77
102
|
}
|
|
78
103
|
|
|
79
|
-
/** `npm install` (fresh) or `npm update` (existing) in a skill scripts dir. */
|
|
80
|
-
function npmInstall(dir, upgrade = false, extraEnv = {}) {
|
|
104
|
+
/** `npm install` (fresh) or `npm update` (existing) in a skill scripts dir. Async — keeps spinner alive. */
|
|
105
|
+
async function npmInstall(dir, upgrade = false, extraEnv = {}) {
|
|
81
106
|
const args = upgrade ? ['update', '--no-audit', '--no-fund'] : ['install', '--no-audit', '--no-fund'];
|
|
82
107
|
const env = { ...process.env, ...extraEnv };
|
|
83
|
-
|
|
108
|
+
const r = await runAsync('npm', args, { cwd: dir, env });
|
|
109
|
+
return r.ok;
|
|
84
110
|
}
|
|
85
111
|
|
|
86
112
|
/**
|
|
87
113
|
* Download the Puppeteer Chromium browser for chrome-devtools.
|
|
88
114
|
* Prefer puppeteer's own install script (cache-aware, same as its postinstall);
|
|
89
|
-
* fall back to the bundled @puppeteer/browsers CLI.
|
|
90
|
-
* avoided — puppeteer ships no `puppeteer` bin under that invocation and `--yes`
|
|
91
|
-
* would refetch from the registry.
|
|
115
|
+
* fall back to the bundled @puppeteer/browsers CLI.
|
|
92
116
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
117
|
+
* Non-interactive (default): async — keeps spinner alive during download.
|
|
118
|
+
* Interactive: sync with stdio inherit — native progress bar renders in TTY.
|
|
95
119
|
*/
|
|
96
|
-
function installChromium(scriptsDir, interactive = false) {
|
|
97
|
-
const stdioOpt = interactive ? 'inherit' : 'pipe';
|
|
120
|
+
async function installChromium(scriptsDir, interactive = false) {
|
|
98
121
|
// Absolute path so it resolves regardless of the child process cwd.
|
|
99
122
|
const installMjs = path.resolve(scriptsDir, 'node_modules', 'puppeteer', 'install.mjs');
|
|
100
123
|
if (fs.existsSync(installMjs)) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
124
|
+
if (interactive) {
|
|
125
|
+
return run('node', [installMjs], { cwd: scriptsDir, stdio: 'inherit' }).ok;
|
|
126
|
+
}
|
|
127
|
+
const r = await runAsync('node', [installMjs], { cwd: scriptsDir });
|
|
128
|
+
return r.ok;
|
|
129
|
+
}
|
|
130
|
+
if (interactive) {
|
|
131
|
+
return run('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], { cwd: scriptsDir, stdio: 'inherit' }).ok;
|
|
104
132
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
return run('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], opts).ok;
|
|
133
|
+
const r = await runAsync('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], { cwd: scriptsDir });
|
|
134
|
+
return r.ok;
|
|
108
135
|
}
|
|
109
136
|
|
|
110
137
|
/** Does a skill scripts dir declare a given npm dependency? */
|
|
@@ -117,9 +144,10 @@ function pkgHasDep(scriptsDir, name) {
|
|
|
117
144
|
}
|
|
118
145
|
}
|
|
119
146
|
|
|
120
|
-
/** Download the Playwright Chromium browser (for the pptx html2pptx workflow). */
|
|
121
|
-
function installPlaywrightBrowser(scriptsDir) {
|
|
122
|
-
|
|
147
|
+
/** Download the Playwright Chromium browser (for the pptx html2pptx workflow). Async — keeps spinner alive. */
|
|
148
|
+
async function installPlaywrightBrowser(scriptsDir) {
|
|
149
|
+
const r = await runAsync('npx', ['--yes', 'playwright', 'install', 'chromium'], { cwd: scriptsDir });
|
|
150
|
+
return r.ok;
|
|
123
151
|
}
|
|
124
152
|
|
|
125
153
|
/**
|
|
@@ -65,7 +65,7 @@ function setupPython(ctx, skillsDir) {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** Run skill-local npm installs/upgrades + browser binaries for a platform's skills. */
|
|
68
|
-
function setupNode(ctx, skillsDir) {
|
|
68
|
+
async function setupNode(ctx, skillsDir) {
|
|
69
69
|
// Detect system Chrome before npm install — skip ~300MB Chromium download if found
|
|
70
70
|
const chromeScripts = path.join(skillsDir, 'chrome-devtools', 'scripts');
|
|
71
71
|
const hasChromePkg = fs.existsSync(path.join(chromeScripts, 'package.json'));
|
|
@@ -78,7 +78,7 @@ function setupNode(ctx, skillsDir) {
|
|
|
78
78
|
const extraEnv = (skill === 'chrome-devtools' && systemChrome)
|
|
79
79
|
? { PUPPETEER_SKIP_DOWNLOAD: 'true' }
|
|
80
80
|
: {};
|
|
81
|
-
const ok = dep.npmInstall(dir, hasNodeModules, extraEnv);
|
|
81
|
+
const ok = await dep.npmInstall(dir, hasNodeModules, extraEnv);
|
|
82
82
|
|
|
83
83
|
if (ok) {
|
|
84
84
|
ctx.ui.stopSpinner(ctx.t('npmInstalled', { skill }));
|
|
@@ -90,7 +90,7 @@ function setupNode(ctx, skillsDir) {
|
|
|
90
90
|
// Skills using Playwright (e.g. pptx) also need a browser binary.
|
|
91
91
|
if (dep.pkgHasDep(dir, 'playwright')) {
|
|
92
92
|
ctx.ui.startSpinner(ctx.t('playwrightInstalling', { skill }));
|
|
93
|
-
const pw = dep.installPlaywrightBrowser(dir);
|
|
93
|
+
const pw = await dep.installPlaywrightBrowser(dir);
|
|
94
94
|
ctx.ui.stopSpinner(pw ? ctx.t('playwrightReady', { skill }) : ctx.t('playwrightSkipped', { skill }));
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -102,7 +102,7 @@ function setupNode(ctx, skillsDir) {
|
|
|
102
102
|
ctx.ui.info(ctx.t('chromiumSystemChromeFound', { path: systemChrome }));
|
|
103
103
|
} else {
|
|
104
104
|
ctx.ui.info(ctx.t('chromiumInstalling'));
|
|
105
|
-
const ok = dep.installChromium(chromeScripts, true);
|
|
105
|
+
const ok = await dep.installChromium(chromeScripts, true);
|
|
106
106
|
ctx.ui.info(ok ? '✓ ' + ctx.t('chromiumReady') : ctx.t('chromiumSkipped'));
|
|
107
107
|
}
|
|
108
108
|
}
|
|
@@ -128,7 +128,7 @@ async function setupSkillDeps(ctx) {
|
|
|
128
128
|
|
|
129
129
|
for (const key of ctx.platforms) {
|
|
130
130
|
setupPython(ctx, PLATFORMS[key].skillsDir);
|
|
131
|
-
setupNode(ctx, PLATFORMS[key].skillsDir);
|
|
131
|
+
await setupNode(ctx, PLATFORMS[key].skillsDir);
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
guideManual(ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.9",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|