@pimzino/claude-code-spec-workflow
Version:
Automated workflows for Claude Code. Includes spec-driven development (Requirements → Design → Tasks → Implementation) with intelligent task execution, optional steering documents and streamlined bug fix workflow (Report → Analyze → Fix → Verify). We have
175 lines • 7.26 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.CloudflareProvider = void 0;
const types_1 = require("./types");
const events_1 = require("events");
const child_process_1 = require("child_process");
class CloudflareTunnelInstance extends events_1.EventEmitter {
constructor(_url) {
super();
this._url = _url;
this.status = 'active';
this.createdAt = new Date();
this.provider = 'cloudflare';
}
get url() {
return this._url;
}
setProcess(process) {
this.process = process;
}
async close() {
if (this.status === 'closing') {
return;
}
this.status = 'closing';
if (this.process) {
const proc = this.process;
return new Promise((resolve) => {
const timeout = setTimeout(() => {
proc.kill('SIGKILL');
resolve();
}, 5000);
proc.once('exit', () => {
clearTimeout(timeout);
resolve();
});
proc.kill('SIGTERM');
});
}
}
async getHealth() {
if (this.status !== 'active') {
return {
healthy: false,
error: `Tunnel is ${this.status}`
};
}
return {
healthy: true,
latency: 0
};
}
}
class CloudflareProvider {
constructor(_config) {
this._config = _config;
this.name = 'cloudflare';
}
async isAvailable() {
// For now, we'll use the cloudflared CLI approach since the npm package
// requires setup and domain configuration
// The cloudflared-tunnel package requires a one-time setup which is too complex
// for a simple tunnel use case
try {
const { exec } = await Promise.resolve().then(() => __importStar(require('child_process')));
const { promisify } = await Promise.resolve().then(() => __importStar(require('util')));
const execPromise = promisify(exec);
const { stdout } = await execPromise('which cloudflared');
return !!stdout.trim();
}
catch {
return false;
}
}
async validateConfig() {
const available = await this.isAvailable();
if (!available) {
throw new types_1.TunnelProviderError(this.name, 'CLOUDFLARED_NOT_FOUND', 'cloudflared CLI not found. Please install it from https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/installation/');
}
}
async createTunnel(port, _options) {
await this.validateConfig();
return new Promise((resolve, reject) => {
// Use cloudflared CLI for simplicity
// The npm packages require complex setup with domains
const args = ['tunnel', '--url', `http://localhost:${port}`];
const cloudflared = (0, child_process_1.spawn)('cloudflared', args, {
stdio: ['ignore', 'pipe', 'pipe'],
env: { ...process.env }
});
let urlFound = false;
let errorOutput = '';
const handleOutput = (data) => {
const output = data.toString();
// Look for the tunnel URL in the output
const urlMatch = output.match(/https:\/\/[a-zA-Z0-9-]+\.trycloudflare\.com/);
if (urlMatch && !urlFound) {
urlFound = true;
const url = urlMatch[0];
// Create new instance with URL
const tunnelInstance = new CloudflareTunnelInstance(url);
tunnelInstance.setProcess(cloudflared);
resolve(tunnelInstance);
}
// Capture any error messages
if (output.toLowerCase().includes('error')) {
errorOutput += output;
}
};
cloudflared.stdout.on('data', handleOutput);
cloudflared.stderr.on('data', handleOutput);
cloudflared.on('error', (error) => {
reject(new types_1.TunnelProviderError(this.name, 'CLOUDFLARED_START_ERROR', `Failed to start cloudflared: ${error.message}`, 'Could not start Cloudflare tunnel.', [
'Check that cloudflared is properly installed',
'Verify your internet connection',
'Try running: cloudflared tunnel --url http://localhost:' + port
]));
});
cloudflared.on('exit', (code, signal) => {
if (!urlFound) {
reject(new types_1.TunnelProviderError(this.name, 'CLOUDFLARED_EXIT_EARLY', `cloudflared exited unexpectedly (code: ${code}, signal: ${signal}). ${errorOutput}`, 'Cloudflare tunnel exited before establishing connection.', [
'Check that the port is not already in use',
'Verify cloudflared has proper permissions',
'Check your firewall settings'
]));
}
});
// Timeout if URL not found within 30 seconds
setTimeout(() => {
if (!urlFound) {
cloudflared.kill();
reject(new types_1.TunnelProviderError(this.name, 'CLOUDFLARED_TIMEOUT', 'Timeout waiting for tunnel URL from cloudflared', 'Cloudflare tunnel took too long to start.', [
'Check your internet connection',
'Try running cloudflared manually'
]));
}
}, 30000);
});
}
}
exports.CloudflareProvider = CloudflareProvider;
//# sourceMappingURL=cloudflare-provider-native.js.map