refactor: deduplicate help text, cross-platform validate-pack, fix dev.js
- Extract duplicated help text from loader.ts and cli.ts into shared help-text.ts module (single source of truth) - Convert validate-pack.sh to Node.js for Windows compatibility - Fix dev.js using unnecessary npx for tsc (it's a devDependency, use node_modules/.bin/tsc directly)
This commit is contained in:
parent
9c8a24042f
commit
a79e953caa
6 changed files with 140 additions and 34 deletions
|
|
@ -61,7 +61,7 @@
|
|||
"pi:uninstall-global": "node scripts/uninstall-pi-global.js",
|
||||
"sync-pkg-version": "node scripts/sync-pkg-version.cjs",
|
||||
"sync-platform-versions": "node native/scripts/sync-platform-versions.cjs",
|
||||
"validate-pack": "bash scripts/validate-pack.sh",
|
||||
"validate-pack": "node scripts/validate-pack.js",
|
||||
"typecheck:extensions": "tsc --noEmit --project tsconfig.extensions.json",
|
||||
"prepublishOnly": "npm run sync-pkg-version && npm run sync-platform-versions && git diff --exit-code || (echo 'ERROR: version sync changed files — commit them before publishing' && exit 1) && npm run build && npm run typecheck:extensions && npm run validate-pack"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ const procs = [
|
|||
spawn('node', [resolve(__dirname, 'watch-resources.js')], {
|
||||
cwd: root, stdio: 'inherit'
|
||||
}),
|
||||
spawn('npx', ['tsc', '--watch'], {
|
||||
spawn(resolve(root, 'node_modules', '.bin', 'tsc'), ['--watch'], {
|
||||
cwd: root, stdio: 'inherit'
|
||||
})
|
||||
]
|
||||
|
|
|
|||
116
scripts/validate-pack.js
Normal file
116
scripts/validate-pack.js
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// validate-pack.js — Verify the npm tarball is installable before publishing.
|
||||
//
|
||||
// Usage: npm run validate-pack (or node scripts/validate-pack.js)
|
||||
// Exit 0 = safe to publish, Exit 1 = broken package.
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
const ROOT = resolve(__dirname, '..');
|
||||
|
||||
let tarball = null;
|
||||
let installDir = null;
|
||||
|
||||
try {
|
||||
// --- Guard: workspace packages must not have @gsd/* cross-deps ---
|
||||
console.log('==> Checking workspace packages for @gsd/* cross-deps...');
|
||||
const workspaces = ['native', 'pi-agent-core', 'pi-ai', 'pi-coding-agent', 'pi-tui'];
|
||||
let crossFailed = false;
|
||||
|
||||
for (const ws of workspaces) {
|
||||
const pkgPath = join(ROOT, 'packages', ws, 'package.json');
|
||||
if (!existsSync(pkgPath)) continue;
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
||||
const deps = Object.keys(pkg.dependencies || {}).filter(d => d.startsWith('@gsd/'));
|
||||
if (deps.length) {
|
||||
console.log(` LEAKED in ${ws}: ${deps.join(', ')}`);
|
||||
crossFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (crossFailed) {
|
||||
console.log('ERROR: Workspace packages have @gsd/* cross-dependencies.');
|
||||
console.log(' These cause 404s when npm resolves them from the registry.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(' No @gsd/* cross-dependencies.');
|
||||
|
||||
// --- Pack tarball ---
|
||||
console.log('==> Packing tarball...');
|
||||
const packOutput = execSync('npm pack --ignore-scripts', {
|
||||
cwd: ROOT,
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
const tarballName = packOutput.trim().split('\n').pop();
|
||||
tarball = join(ROOT, tarballName);
|
||||
|
||||
if (!existsSync(tarball)) {
|
||||
console.log('ERROR: npm pack produced no tarball');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const stats = execSync(`du -h "${tarball}"`, { encoding: 'utf8' }).split('\t')[0].trim();
|
||||
console.log(`==> Tarball: ${tarballName} (${stats} compressed)`);
|
||||
|
||||
// --- Check critical files using tar listing ---
|
||||
console.log('==> Checking critical files...');
|
||||
const tarList = execSync(`tar tzf "${tarball}"`, { encoding: 'utf8', maxBuffer: 50 * 1024 * 1024 });
|
||||
|
||||
const requiredFiles = [
|
||||
'dist/loader.js',
|
||||
'packages/pi-coding-agent/dist/index.js',
|
||||
'scripts/link-workspace-packages.cjs',
|
||||
];
|
||||
|
||||
let missing = false;
|
||||
for (const required of requiredFiles) {
|
||||
if (!tarList.includes(`package/${required}`)) {
|
||||
console.log(` MISSING: ${required}`);
|
||||
missing = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (missing) {
|
||||
console.log('ERROR: Critical files missing from tarball.');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(' Critical files present.');
|
||||
|
||||
// --- Install test ---
|
||||
console.log('==> Testing install in isolated directory...');
|
||||
installDir = mkdtempSync(join(tmpdir(), 'validate-pack-'));
|
||||
writeFileSync(join(installDir, 'package.json'), JSON.stringify({ name: 'test-install', version: '1.0.0', private: true }, null, 2));
|
||||
|
||||
try {
|
||||
const installOutput = execSync(`npm install "${tarball}"`, {
|
||||
cwd: installDir,
|
||||
encoding: 'utf8',
|
||||
stdio: ['pipe', 'pipe', 'pipe'],
|
||||
});
|
||||
console.log(installOutput);
|
||||
console.log('==> Install succeeded.');
|
||||
} catch (err) {
|
||||
console.log('');
|
||||
console.log('ERROR: npm install of tarball failed.');
|
||||
if (err.stdout) console.log(err.stdout);
|
||||
if (err.stderr) console.log(err.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('Package is installable. Safe to publish.');
|
||||
process.exit(0);
|
||||
} finally {
|
||||
if (installDir && existsSync(installDir)) {
|
||||
rmSync(installDir, { recursive: true, force: true });
|
||||
}
|
||||
if (tarball && existsSync(tarball)) {
|
||||
rmSync(tarball, { force: true });
|
||||
}
|
||||
}
|
||||
18
src/cli.ts
18
src/cli.ts
|
|
@ -19,6 +19,7 @@ import { getPiDefaultModelAndProvider, migratePiCredentials } from './pi-migrati
|
|||
import { shouldRunOnboarding, runOnboarding } from './onboarding.js'
|
||||
import chalk from 'chalk'
|
||||
import { checkForUpdates } from './update-check.js'
|
||||
import { printHelp } from './help-text.js'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Minimal CLI arg parser — detects print/subagent mode flags
|
||||
|
|
@ -79,22 +80,7 @@ function parseCliArgs(argv: string[]): CliFlags {
|
|||
process.stdout.write((process.env.GSD_VERSION || '0.0.0') + '\n')
|
||||
process.exit(0)
|
||||
} else if (arg === '--help' || arg === '-h') {
|
||||
process.stdout.write(`GSD v${process.env.GSD_VERSION || '0.0.0'} — Get Shit Done\n\n`)
|
||||
process.stdout.write('Usage: gsd [options] [message...]\n\n')
|
||||
process.stdout.write('Options:\n')
|
||||
process.stdout.write(' --mode <text|json|rpc> Output mode (default: interactive)\n')
|
||||
process.stdout.write(' --print, -p Single-shot print mode\n')
|
||||
process.stdout.write(' --continue, -c Resume the most recent session\n')
|
||||
process.stdout.write(' --model <id> Override model (e.g. claude-opus-4-6)\n')
|
||||
process.stdout.write(' --no-session Disable session persistence\n')
|
||||
process.stdout.write(' --extension <path> Load additional extension\n')
|
||||
process.stdout.write(' --tools <a,b,c> Restrict available tools\n')
|
||||
process.stdout.write(' --list-models [search] List available models and exit\n')
|
||||
process.stdout.write(' --version, -v Print version and exit\n')
|
||||
process.stdout.write(' --help, -h Print this help and exit\n')
|
||||
process.stdout.write('\nSubcommands:\n')
|
||||
process.stdout.write(' config Re-run the setup wizard\n')
|
||||
process.stdout.write(' update Update GSD to the latest version\n')
|
||||
printHelp(process.env.GSD_VERSION || '0.0.0')
|
||||
process.exit(0)
|
||||
} else if (!arg.startsWith('--') && !arg.startsWith('-')) {
|
||||
flags.messages.push(arg)
|
||||
|
|
|
|||
18
src/help-text.ts
Normal file
18
src/help-text.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export function printHelp(version: string): void {
|
||||
process.stdout.write(`GSD v${version} — Get Shit Done\n\n`)
|
||||
process.stdout.write('Usage: gsd [options] [message...]\n\n')
|
||||
process.stdout.write('Options:\n')
|
||||
process.stdout.write(' --mode <text|json|rpc> Output mode (default: interactive)\n')
|
||||
process.stdout.write(' --print, -p Single-shot print mode\n')
|
||||
process.stdout.write(' --continue, -c Resume the most recent session\n')
|
||||
process.stdout.write(' --model <id> Override model (e.g. claude-opus-4-6)\n')
|
||||
process.stdout.write(' --no-session Disable session persistence\n')
|
||||
process.stdout.write(' --extension <path> Load additional extension\n')
|
||||
process.stdout.write(' --tools <a,b,c> Restrict available tools\n')
|
||||
process.stdout.write(' --list-models [search] List available models and exit\n')
|
||||
process.stdout.write(' --version, -v Print version and exit\n')
|
||||
process.stdout.write(' --help, -h Print this help and exit\n')
|
||||
process.stdout.write('\nSubcommands:\n')
|
||||
process.stdout.write(' config Re-run the setup wizard\n')
|
||||
process.stdout.write(' update Update GSD to the latest version\n')
|
||||
}
|
||||
|
|
@ -28,22 +28,8 @@ if (firstArg === '--help' || firstArg === '-h') {
|
|||
const pkg = JSON.parse(readFileSync(join(gsdRoot, 'package.json'), 'utf-8'))
|
||||
version = pkg.version || version
|
||||
} catch { /* ignore */ }
|
||||
process.stdout.write(`GSD v${version} — Get Shit Done\n\n`)
|
||||
process.stdout.write('Usage: gsd [options] [message...]\n\n')
|
||||
process.stdout.write('Options:\n')
|
||||
process.stdout.write(' --mode <text|json|rpc> Output mode (default: interactive)\n')
|
||||
process.stdout.write(' --print, -p Single-shot print mode\n')
|
||||
process.stdout.write(' --continue, -c Resume the most recent session\n')
|
||||
process.stdout.write(' --model <id> Override model (e.g. claude-opus-4-6)\n')
|
||||
process.stdout.write(' --no-session Disable session persistence\n')
|
||||
process.stdout.write(' --extension <path> Load additional extension\n')
|
||||
process.stdout.write(' --tools <a,b,c> Restrict available tools\n')
|
||||
process.stdout.write(' --list-models [search] List available models and exit\n')
|
||||
process.stdout.write(' --version, -v Print version and exit\n')
|
||||
process.stdout.write(' --help, -h Print this help and exit\n')
|
||||
process.stdout.write('\nSubcommands:\n')
|
||||
process.stdout.write(' config Re-run the setup wizard\n')
|
||||
process.stdout.write(' update Update GSD to the latest version\n')
|
||||
const { printHelp } = await import('./help-text.js')
|
||||
printHelp(version)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue