Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 59 additions & 16 deletions packaging/npm/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -151,35 +151,61 @@ function runNativeBinary(binaryPath) {
process.on('SIGTERM', () => server.kill('SIGTERM'));
}

const isWindows = process.platform === 'win32';

// The Flutter and Dart entrypoints on Windows are `.bat` scripts, which
// CreateProcess cannot execute directly — spawn() fails with UNKNOWN or ENOENT
// unless it goes through a shell. A shell does no argument quoting for us, so
// anything containing whitespace has to be quoted by hand.
function quoteArg(value) {
return isWindows && /[\s&|<>^()]/.test(value) ? `"${value}"` : value;
}

// Run using Dart
function runWithDart() {
const dartDir = path.join(__dirname, '..', 'dart');
const serverScript = path.join(dartDir, 'bin', 'server.dart');

// Check if Dart is installed
try {
execSync('dart --version', { stdio: 'ignore' });
} catch (e) {
console.error('Error: Dart SDK not found. Please install Flutter/Dart first.');
if (!fs.existsSync(serverScript)) {
console.error('Error: Server script not found at:', serverScript);
console.error('The npm package looks incomplete — try reinstalling flutter-skill.');
process.exit(1);
}

// The vendored package depends on package:flutter, so `dart pub get` can
// never resolve it ("Because flutter_skill requires the Flutter SDK, version
// solving failed"). Checking only for Dart let that failure through and the
// run then died with a confusing "Couldn't resolve the package
// 'flutter_skill'" instead of naming the real prerequisite.
if (!checkFlutter()) {
console.error('Error: Flutter SDK not found.');
console.error('flutter-skill runs on the Flutter SDK — the Dart SDK alone cannot');
console.error('resolve its dependencies. Install Flutter and make sure it is on PATH:');
console.error(' https://docs.flutter.dev/get-started/install');
process.exit(1);
}

// Check if server script exists
if (!fs.existsSync(serverScript)) {
console.error('Error: Server script not found at:', serverScript);
try {
execSync('dart --version', { stdio: 'ignore' });
} catch (e) {
console.error('Error: `dart` was not found on PATH, but `flutter` was.');
console.error('Add the Flutter SDK\'s bin directory to PATH so `dart` resolves too.');
process.exit(1);
}

// Get dependencies silently
// A failure here used to be swallowed, leaving the run to fail later with an
// unrelated-looking import error. Report it and stop.
try {
const pubCmd = checkFlutter() ? 'flutter' : 'dart';
execSync(`${pubCmd} pub get`, {
execSync('flutter pub get', {
cwd: dartDir,
stdio: ['ignore', 'pipe', 'pipe']
});
} catch (e) {
// Ignore pub get errors
console.error('[flutter-skill] `flutter pub get` failed in', dartDir);
const details = ((e.stderr || '') + (e.stdout || '')).toString().trim();
if (details) console.error(details);
console.error('[flutter-skill] Cannot start without resolved dependencies.');
process.exit(1);
}

// Start with Dart
Expand All @@ -188,10 +214,27 @@ function runWithDart() {
args.push('server');
}

const dartArgs = ['run', serverScript, ...args];
const server = spawn('dart', dartArgs, {
cwd: dartDir,
stdio: 'inherit'
const dartArgs = ['run', serverScript, ...args].map(quoteArg);

// Same launch-failure handling as the native path: spawn() reports failures
// synchronously on some platforms and only via the 'error' event on others.
// There is no further fallback beyond Dart, so both paths exit with a
// diagnostic rather than crashing on an uncaught exception.
let server;
try {
server = spawn('dart', dartArgs, {
cwd: dartDir,
stdio: 'inherit',
shell: isWindows
});
} catch (err) {
console.error(`[flutter-skill] Failed to start the Dart runtime (${err.code || err.message})`);
process.exit(1);
}

server.on('error', (err) => {
console.error(`[flutter-skill] Failed to start the Dart runtime (${err.code || err.message})`);
process.exit(1);
});

server.on('close', (code) => {
Expand Down
12 changes: 10 additions & 2 deletions packaging/npm/scripts/postinstall.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ function downloadBinary(url, destPath) {
}

if (response.statusCode !== 200) {
// createWriteStream has already created an empty file. Leaving it
// behind makes every later install believe the binary is present.
file.close(() => fs.unlink(destPath, () => {}));
reject(new Error(`HTTP ${response.statusCode}`));
return;
}
Expand Down Expand Up @@ -68,7 +71,10 @@ function downloadBinary(url, destPath) {
console.log('\n[flutter-skill] Native binary installed successfully!');
resolve(destPath);
});
}).on('error', reject);
}).on('error', (err) => {
file.close(() => fs.unlink(destPath, () => {}));
reject(err);
});
};

request(url);
Expand All @@ -84,7 +90,9 @@ async function main() {

const localPath = path.join(binDir, `${binaryName}-v${VERSION}`);

if (fs.existsSync(localPath)) {
// A zero-byte file is the residue of an interrupted or 404'd download, not an
// installed binary. Treat it as absent so the download is retried.
if (fs.existsSync(localPath) && fs.statSync(localPath).size > 0) {
// Re-apply execute permission in case a previous install left the binary
// without +x (e.g. chmod failed silently inside a restricted npm sandbox).
try { fs.chmodSync(localPath, 0o755); } catch (_) {}
Expand Down
Loading