claude-cli-advanced-starter-pack 1.0.16 → 1.8.0
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/OVERVIEW.md +5 -1
- package/README.md +241 -132
- package/bin/gtask.js +53 -0
- package/package.json +1 -1
- package/src/cli/menu.js +27 -0
- package/src/commands/explore-mcp/mcp-registry.js +99 -0
- package/src/commands/init.js +309 -80
- package/src/commands/install-panel-hook.js +108 -0
- package/src/commands/install-scripts.js +232 -0
- package/src/commands/install-skill.js +220 -0
- package/src/commands/panel.js +297 -0
- package/src/commands/setup-wizard.js +4 -3
- package/src/commands/test-setup.js +4 -5
- package/src/data/releases.json +209 -0
- package/src/panel/queue.js +188 -0
- package/templates/commands/ask-claude.template.md +118 -0
- package/templates/commands/ccasp-panel.template.md +72 -0
- package/templates/commands/ccasp-setup.template.md +470 -79
- package/templates/commands/create-smoke-test.template.md +186 -0
- package/templates/commands/project-impl.template.md +9 -113
- package/templates/commands/refactor-check.template.md +112 -0
- package/templates/commands/refactor-cleanup.template.md +144 -0
- package/templates/commands/refactor-prep.template.md +192 -0
- package/templates/docs/AI_ARCHITECTURE_CONSTITUTION.template.md +198 -0
- package/templates/docs/DETAILED_GOTCHAS.template.md +347 -0
- package/templates/docs/PHASE-DEV-CHECKLIST.template.md +241 -0
- package/templates/docs/PROGRESS_JSON_TEMPLATE.json +117 -0
- package/templates/docs/background-agent.template.md +264 -0
- package/templates/hooks/autonomous-decision-logger.template.js +207 -0
- package/templates/hooks/branch-merge-checker.template.js +272 -0
- package/templates/hooks/context-injector.template.js +261 -0
- package/templates/hooks/git-commit-tracker.template.js +267 -0
- package/templates/hooks/happy-mode-detector.template.js +214 -0
- package/templates/hooks/happy-title-generator.template.js +260 -0
- package/templates/hooks/issue-completion-detector.template.js +205 -0
- package/templates/hooks/panel-queue-reader.template.js +83 -0
- package/templates/hooks/phase-validation-gates.template.js +307 -0
- package/templates/hooks/session-id-generator.template.js +236 -0
- package/templates/hooks/token-budget-loader.template.js +234 -0
- package/templates/hooks/token-usage-monitor.template.js +193 -0
- package/templates/hooks/tool-output-cacher.template.js +219 -0
- package/templates/patterns/README.md +129 -0
- package/templates/patterns/l1-l2-orchestration.md +189 -0
- package/templates/patterns/multi-phase-orchestration.md +258 -0
- package/templates/patterns/two-tier-query-pipeline.md +192 -0
- package/templates/scripts/README.md +109 -0
- package/templates/scripts/analyze-delegation-log.js +299 -0
- package/templates/scripts/autonomous-decision-logger.js +277 -0
- package/templates/scripts/git-history-analyzer.py +269 -0
- package/templates/scripts/phase-validation-gates.js +307 -0
- package/templates/scripts/poll-deployment-status.js +260 -0
- package/templates/scripts/roadmap-scanner.js +263 -0
- package/templates/scripts/validate-deployment.js +293 -0
- package/templates/skills/agent-creator/skill.json +18 -0
- package/templates/skills/agent-creator/skill.md +335 -0
- package/templates/skills/hook-creator/skill.json +18 -0
- package/templates/skills/hook-creator/skill.md +318 -0
- package/templates/skills/panel/skill.json +18 -0
- package/templates/skills/panel/skill.md +90 -0
- package/templates/skills/rag-agent-creator/skill.json +18 -0
- package/templates/skills/rag-agent-creator/skill.md +307 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Phase Validation Gates
|
|
4
|
+
*
|
|
5
|
+
* 5-gate validation before phase transitions:
|
|
6
|
+
* 1. EXIST - Required files and artifacts exist
|
|
7
|
+
* 2. INIT - Initialization completed successfully
|
|
8
|
+
* 3. REGISTER - Components registered/configured
|
|
9
|
+
* 4. INVOKE - Functionality works when invoked
|
|
10
|
+
* 5. PROPAGATE - Changes propagated to dependent systems
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* node phase-validation-gates.js --phase 1 --config ./validation.json
|
|
14
|
+
* node phase-validation-gates.js --progress ./PROGRESS.json
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { existsSync, readFileSync } from 'fs';
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
import { resolve } from 'path';
|
|
20
|
+
|
|
21
|
+
const GATES = ['EXIST', 'INIT', 'REGISTER', 'INVOKE', 'PROPAGATE'];
|
|
22
|
+
|
|
23
|
+
class PhaseValidator {
|
|
24
|
+
constructor(options = {}) {
|
|
25
|
+
this.phase = options.phase;
|
|
26
|
+
this.configPath = options.configPath;
|
|
27
|
+
this.progressPath = options.progressPath || 'PROGRESS.json';
|
|
28
|
+
this.verbose = options.verbose || false;
|
|
29
|
+
this.config = null;
|
|
30
|
+
this.results = {
|
|
31
|
+
phase: this.phase,
|
|
32
|
+
gates: {},
|
|
33
|
+
passed: false,
|
|
34
|
+
timestamp: new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async validate() {
|
|
39
|
+
await this.loadConfig();
|
|
40
|
+
|
|
41
|
+
console.log(`\n🔒 Phase ${this.phase} Validation\n`);
|
|
42
|
+
console.log('Gate Status Checks');
|
|
43
|
+
console.log('-'.repeat(60));
|
|
44
|
+
|
|
45
|
+
for (const gate of GATES) {
|
|
46
|
+
const gateConfig = this.config.gates?.[gate] || {};
|
|
47
|
+
const result = await this.validateGate(gate, gateConfig);
|
|
48
|
+
this.results.gates[gate] = result;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this.results.passed = Object.values(this.results.gates)
|
|
52
|
+
.every(g => g.passed);
|
|
53
|
+
|
|
54
|
+
this.printSummary();
|
|
55
|
+
return this.results;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async loadConfig() {
|
|
59
|
+
if (this.configPath && existsSync(this.configPath)) {
|
|
60
|
+
this.config = JSON.parse(readFileSync(this.configPath, 'utf8'));
|
|
61
|
+
} else if (existsSync(this.progressPath)) {
|
|
62
|
+
const progress = JSON.parse(readFileSync(this.progressPath, 'utf8'));
|
|
63
|
+
this.config = progress.phases?.[this.phase - 1]?.validation || {};
|
|
64
|
+
} else {
|
|
65
|
+
this.config = this.getDefaultConfig();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getDefaultConfig() {
|
|
70
|
+
return {
|
|
71
|
+
gates: {
|
|
72
|
+
EXIST: {
|
|
73
|
+
files: ['package.json'],
|
|
74
|
+
directories: ['src/', 'node_modules/'],
|
|
75
|
+
},
|
|
76
|
+
INIT: {
|
|
77
|
+
commands: ['npm --version'],
|
|
78
|
+
},
|
|
79
|
+
REGISTER: {
|
|
80
|
+
files: ['.claude/settings.json'],
|
|
81
|
+
},
|
|
82
|
+
INVOKE: {
|
|
83
|
+
commands: ['npm run --silent test 2>/dev/null || echo "no tests"'],
|
|
84
|
+
},
|
|
85
|
+
PROPAGATE: {
|
|
86
|
+
commands: ['git status --porcelain'],
|
|
87
|
+
expectEmpty: true,
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async validateGate(gate, config) {
|
|
94
|
+
const result = {
|
|
95
|
+
gate,
|
|
96
|
+
passed: true,
|
|
97
|
+
checks: [],
|
|
98
|
+
errors: [],
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
switch (gate) {
|
|
103
|
+
case 'EXIST':
|
|
104
|
+
await this.checkExist(config, result);
|
|
105
|
+
break;
|
|
106
|
+
case 'INIT':
|
|
107
|
+
await this.checkInit(config, result);
|
|
108
|
+
break;
|
|
109
|
+
case 'REGISTER':
|
|
110
|
+
await this.checkRegister(config, result);
|
|
111
|
+
break;
|
|
112
|
+
case 'INVOKE':
|
|
113
|
+
await this.checkInvoke(config, result);
|
|
114
|
+
break;
|
|
115
|
+
case 'PROPAGATE':
|
|
116
|
+
await this.checkPropagate(config, result);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
} catch (error) {
|
|
120
|
+
result.passed = false;
|
|
121
|
+
result.errors.push(error.message);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const icon = result.passed ? '✅' : '❌';
|
|
125
|
+
const checkCount = `${result.checks.filter(c => c.passed).length}/${result.checks.length}`;
|
|
126
|
+
console.log(`${gate.padEnd(12)} ${icon} ${checkCount} checks passed`);
|
|
127
|
+
|
|
128
|
+
if (this.verbose && result.errors.length > 0) {
|
|
129
|
+
for (const error of result.errors) {
|
|
130
|
+
console.log(` └─ ${error}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async checkExist(config, result) {
|
|
138
|
+
// Check files
|
|
139
|
+
for (const file of config.files || []) {
|
|
140
|
+
const exists = existsSync(resolve(process.cwd(), file));
|
|
141
|
+
result.checks.push({ type: 'file', path: file, passed: exists });
|
|
142
|
+
if (!exists) {
|
|
143
|
+
result.passed = false;
|
|
144
|
+
result.errors.push(`Missing file: ${file}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Check directories
|
|
149
|
+
for (const dir of config.directories || []) {
|
|
150
|
+
const exists = existsSync(resolve(process.cwd(), dir));
|
|
151
|
+
result.checks.push({ type: 'directory', path: dir, passed: exists });
|
|
152
|
+
if (!exists) {
|
|
153
|
+
result.passed = false;
|
|
154
|
+
result.errors.push(`Missing directory: ${dir}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async checkInit(config, result) {
|
|
160
|
+
// Run initialization check commands
|
|
161
|
+
for (const cmd of config.commands || []) {
|
|
162
|
+
try {
|
|
163
|
+
execSync(cmd, { encoding: 'utf8', stdio: 'pipe' });
|
|
164
|
+
result.checks.push({ type: 'command', command: cmd, passed: true });
|
|
165
|
+
} catch (error) {
|
|
166
|
+
result.passed = false;
|
|
167
|
+
result.checks.push({ type: 'command', command: cmd, passed: false });
|
|
168
|
+
result.errors.push(`Command failed: ${cmd}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check environment variables
|
|
173
|
+
for (const envVar of config.envVars || []) {
|
|
174
|
+
const exists = !!process.env[envVar];
|
|
175
|
+
result.checks.push({ type: 'envVar', name: envVar, passed: exists });
|
|
176
|
+
if (!exists) {
|
|
177
|
+
result.passed = false;
|
|
178
|
+
result.errors.push(`Missing env var: ${envVar}`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async checkRegister(config, result) {
|
|
184
|
+
// Check config files contain expected content
|
|
185
|
+
for (const file of config.files || []) {
|
|
186
|
+
const filePath = resolve(process.cwd(), file);
|
|
187
|
+
const exists = existsSync(filePath);
|
|
188
|
+
result.checks.push({ type: 'config', path: file, passed: exists });
|
|
189
|
+
|
|
190
|
+
if (!exists) {
|
|
191
|
+
result.passed = false;
|
|
192
|
+
result.errors.push(`Missing config: ${file}`);
|
|
193
|
+
} else if (config.contains?.[file]) {
|
|
194
|
+
const content = readFileSync(filePath, 'utf8');
|
|
195
|
+
for (const expected of config.contains[file]) {
|
|
196
|
+
const found = content.includes(expected);
|
|
197
|
+
result.checks.push({ type: 'content', file, expected, passed: found });
|
|
198
|
+
if (!found) {
|
|
199
|
+
result.passed = false;
|
|
200
|
+
result.errors.push(`Missing in ${file}: ${expected}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
async checkInvoke(config, result) {
|
|
208
|
+
// Run functionality tests
|
|
209
|
+
for (const cmd of config.commands || []) {
|
|
210
|
+
try {
|
|
211
|
+
const output = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' });
|
|
212
|
+
|
|
213
|
+
if (config.expectOutput?.[cmd]) {
|
|
214
|
+
const expected = config.expectOutput[cmd];
|
|
215
|
+
const matches = output.includes(expected);
|
|
216
|
+
result.checks.push({ type: 'invoke', command: cmd, passed: matches });
|
|
217
|
+
if (!matches) {
|
|
218
|
+
result.passed = false;
|
|
219
|
+
result.errors.push(`Output mismatch for: ${cmd}`);
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
result.checks.push({ type: 'invoke', command: cmd, passed: true });
|
|
223
|
+
}
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (!config.allowFailure?.includes(cmd)) {
|
|
226
|
+
result.passed = false;
|
|
227
|
+
result.checks.push({ type: 'invoke', command: cmd, passed: false });
|
|
228
|
+
result.errors.push(`Invoke failed: ${cmd}`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async checkPropagate(config, result) {
|
|
235
|
+
// Check that changes are propagated (git clean, deps synced, etc.)
|
|
236
|
+
for (const cmd of config.commands || []) {
|
|
237
|
+
try {
|
|
238
|
+
const output = execSync(cmd, { encoding: 'utf8', stdio: 'pipe' });
|
|
239
|
+
|
|
240
|
+
if (config.expectEmpty) {
|
|
241
|
+
const isEmpty = output.trim() === '';
|
|
242
|
+
result.checks.push({ type: 'propagate', command: cmd, passed: isEmpty });
|
|
243
|
+
if (!isEmpty) {
|
|
244
|
+
result.passed = false;
|
|
245
|
+
result.errors.push(`Expected empty output: ${cmd}`);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
result.checks.push({ type: 'propagate', command: cmd, passed: true });
|
|
249
|
+
}
|
|
250
|
+
} catch (error) {
|
|
251
|
+
result.passed = false;
|
|
252
|
+
result.checks.push({ type: 'propagate', command: cmd, passed: false });
|
|
253
|
+
result.errors.push(`Propagate check failed: ${cmd}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
printSummary() {
|
|
259
|
+
console.log('\n' + '='.repeat(60));
|
|
260
|
+
console.log(`Phase ${this.phase} Validation: ${this.results.passed ? '✅ PASSED' : '❌ FAILED'}`);
|
|
261
|
+
console.log('='.repeat(60));
|
|
262
|
+
|
|
263
|
+
if (!this.results.passed) {
|
|
264
|
+
console.log('\n❌ Failed Gates:');
|
|
265
|
+
for (const [gate, result] of Object.entries(this.results.gates)) {
|
|
266
|
+
if (!result.passed) {
|
|
267
|
+
console.log(`\n ${gate}:`);
|
|
268
|
+
for (const error of result.errors) {
|
|
269
|
+
console.log(` - ${error}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
console.log('');
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// CLI entry point
|
|
280
|
+
async function main() {
|
|
281
|
+
const args = process.argv.slice(2);
|
|
282
|
+
|
|
283
|
+
const getArg = (name) => {
|
|
284
|
+
const index = args.indexOf(`--${name}`);
|
|
285
|
+
return index >= 0 ? args[index + 1] : null;
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const phase = parseInt(getArg('phase') || '1');
|
|
289
|
+
const configPath = getArg('config');
|
|
290
|
+
const progressPath = getArg('progress');
|
|
291
|
+
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
292
|
+
|
|
293
|
+
const validator = new PhaseValidator({
|
|
294
|
+
phase,
|
|
295
|
+
configPath,
|
|
296
|
+
progressPath,
|
|
297
|
+
verbose,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const results = await validator.validate();
|
|
301
|
+
process.exit(results.passed ? 0 : 1);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Export for use as module
|
|
305
|
+
export { PhaseValidator, GATES };
|
|
306
|
+
|
|
307
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Poll Deployment Status
|
|
4
|
+
*
|
|
5
|
+
* Polls deployment status until complete or timeout.
|
|
6
|
+
* Supports Railway, Cloudflare, Vercel, and custom endpoints.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node poll-deployment-status.js --platform railway --deployment-id abc123
|
|
10
|
+
* node poll-deployment-status.js --platform cloudflare --project-name my-project
|
|
11
|
+
* node poll-deployment-status.js --url https://api.example.com/status
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const POLL_INTERVAL = 5000; // 5 seconds
|
|
15
|
+
const DEFAULT_TIMEOUT = 300000; // 5 minutes
|
|
16
|
+
|
|
17
|
+
class DeploymentPoller {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.platform = options.platform;
|
|
20
|
+
this.deploymentId = options.deploymentId;
|
|
21
|
+
this.projectName = options.projectName;
|
|
22
|
+
this.projectId = options.projectId;
|
|
23
|
+
this.url = options.url;
|
|
24
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
25
|
+
this.verbose = options.verbose || false;
|
|
26
|
+
this.startTime = Date.now();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async poll() {
|
|
30
|
+
console.log(`\n🚀 Polling ${this.platform || 'custom'} deployment...`);
|
|
31
|
+
console.log(` Timeout: ${this.timeout / 1000}s\n`);
|
|
32
|
+
|
|
33
|
+
let lastStatus = null;
|
|
34
|
+
|
|
35
|
+
while (Date.now() - this.startTime < this.timeout) {
|
|
36
|
+
try {
|
|
37
|
+
const status = await this.checkStatus();
|
|
38
|
+
|
|
39
|
+
if (status.status !== lastStatus) {
|
|
40
|
+
this.logStatus(status);
|
|
41
|
+
lastStatus = status.status;
|
|
42
|
+
} else if (this.verbose) {
|
|
43
|
+
process.stdout.write('.');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (status.completed) {
|
|
47
|
+
return this.handleCompletion(status);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
await this.sleep(POLL_INTERVAL);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(`\n❌ Error polling: ${error.message}`);
|
|
53
|
+
if (this.verbose) console.error(error);
|
|
54
|
+
await this.sleep(POLL_INTERVAL);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.error(`\n⏰ Timeout after ${this.timeout / 1000}s`);
|
|
59
|
+
return { success: false, reason: 'timeout' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async checkStatus() {
|
|
63
|
+
switch (this.platform) {
|
|
64
|
+
case 'railway':
|
|
65
|
+
return this.checkRailway();
|
|
66
|
+
case 'cloudflare':
|
|
67
|
+
return this.checkCloudflare();
|
|
68
|
+
case 'vercel':
|
|
69
|
+
return this.checkVercel();
|
|
70
|
+
case 'custom':
|
|
71
|
+
return this.checkCustom();
|
|
72
|
+
default:
|
|
73
|
+
throw new Error(`Unknown platform: ${this.platform}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async checkRailway() {
|
|
78
|
+
const token = process.env.RAILWAY_API_TOKEN;
|
|
79
|
+
if (!token) throw new Error('RAILWAY_API_TOKEN not set');
|
|
80
|
+
|
|
81
|
+
const query = `
|
|
82
|
+
query GetDeployment($id: String!) {
|
|
83
|
+
deployment(id: $id) {
|
|
84
|
+
id
|
|
85
|
+
status
|
|
86
|
+
createdAt
|
|
87
|
+
staticUrl
|
|
88
|
+
meta
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
`;
|
|
92
|
+
|
|
93
|
+
const response = await fetch('https://backboard.railway.app/graphql/v2', {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: {
|
|
96
|
+
'Authorization': `Bearer ${token}`,
|
|
97
|
+
'Content-Type': 'application/json',
|
|
98
|
+
},
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
query,
|
|
101
|
+
variables: { id: this.deploymentId },
|
|
102
|
+
}),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
const data = await response.json();
|
|
106
|
+
if (data.errors) throw new Error(data.errors[0].message);
|
|
107
|
+
|
|
108
|
+
const deployment = data.data.deployment;
|
|
109
|
+
const status = deployment.status;
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
status,
|
|
113
|
+
completed: ['SUCCESS', 'FAILED', 'CRASHED', 'REMOVED'].includes(status),
|
|
114
|
+
success: status === 'SUCCESS',
|
|
115
|
+
url: deployment.staticUrl,
|
|
116
|
+
details: deployment,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async checkCloudflare() {
|
|
121
|
+
const token = process.env.CLOUDFLARE_API_TOKEN;
|
|
122
|
+
const accountId = process.env.CLOUDFLARE_ACCOUNT_ID;
|
|
123
|
+
if (!token) throw new Error('CLOUDFLARE_API_TOKEN not set');
|
|
124
|
+
|
|
125
|
+
// Get latest deployment
|
|
126
|
+
const response = await fetch(
|
|
127
|
+
`https://api.cloudflare.com/client/v4/accounts/${accountId}/pages/projects/${this.projectName}/deployments`,
|
|
128
|
+
{
|
|
129
|
+
headers: {
|
|
130
|
+
'Authorization': `Bearer ${token}`,
|
|
131
|
+
'Content-Type': 'application/json',
|
|
132
|
+
},
|
|
133
|
+
}
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const data = await response.json();
|
|
137
|
+
if (!data.success) throw new Error(data.errors[0].message);
|
|
138
|
+
|
|
139
|
+
const deployment = data.result[0]; // Latest deployment
|
|
140
|
+
const status = deployment.latest_stage?.status || 'unknown';
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
status,
|
|
144
|
+
completed: ['success', 'failure', 'canceled'].includes(status),
|
|
145
|
+
success: status === 'success',
|
|
146
|
+
url: deployment.url,
|
|
147
|
+
details: deployment,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
async checkVercel() {
|
|
152
|
+
const token = process.env.VERCEL_TOKEN;
|
|
153
|
+
if (!token) throw new Error('VERCEL_TOKEN not set');
|
|
154
|
+
|
|
155
|
+
const response = await fetch(
|
|
156
|
+
`https://api.vercel.com/v13/deployments/${this.deploymentId}`,
|
|
157
|
+
{
|
|
158
|
+
headers: {
|
|
159
|
+
'Authorization': `Bearer ${token}`,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
const deployment = await response.json();
|
|
165
|
+
const status = deployment.readyState;
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
status,
|
|
169
|
+
completed: ['READY', 'ERROR', 'CANCELED'].includes(status),
|
|
170
|
+
success: status === 'READY',
|
|
171
|
+
url: deployment.url,
|
|
172
|
+
details: deployment,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async checkCustom() {
|
|
177
|
+
const response = await fetch(this.url);
|
|
178
|
+
const data = await response.json();
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
status: data.status || 'unknown',
|
|
182
|
+
completed: data.completed || false,
|
|
183
|
+
success: data.success || data.status === 'success',
|
|
184
|
+
url: data.url,
|
|
185
|
+
details: data,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
logStatus(status) {
|
|
190
|
+
const elapsed = Math.round((Date.now() - this.startTime) / 1000);
|
|
191
|
+
const icon = status.success ? '✅' :
|
|
192
|
+
status.completed ? '❌' : '🔄';
|
|
193
|
+
console.log(`${icon} [${elapsed}s] Status: ${status.status}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
handleCompletion(status) {
|
|
197
|
+
console.log('\n' + '='.repeat(50));
|
|
198
|
+
if (status.success) {
|
|
199
|
+
console.log('✅ Deployment completed successfully!');
|
|
200
|
+
if (status.url) {
|
|
201
|
+
console.log(`🔗 URL: ${status.url}`);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
console.log('❌ Deployment failed');
|
|
205
|
+
if (this.verbose && status.details) {
|
|
206
|
+
console.log('Details:', JSON.stringify(status.details, null, 2));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
console.log('='.repeat(50) + '\n');
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
success: status.success,
|
|
213
|
+
status: status.status,
|
|
214
|
+
url: status.url,
|
|
215
|
+
duration: Date.now() - this.startTime,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
sleep(ms) {
|
|
220
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// CLI entry point
|
|
225
|
+
async function main() {
|
|
226
|
+
const args = process.argv.slice(2);
|
|
227
|
+
|
|
228
|
+
const getArg = (name) => {
|
|
229
|
+
const index = args.indexOf(`--${name}`);
|
|
230
|
+
return index >= 0 ? args[index + 1] : null;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const hasFlag = (name) => args.includes(`--${name}`);
|
|
234
|
+
|
|
235
|
+
const options = {
|
|
236
|
+
platform: getArg('platform') || 'railway',
|
|
237
|
+
deploymentId: getArg('deployment-id'),
|
|
238
|
+
projectName: getArg('project-name'),
|
|
239
|
+
projectId: getArg('project-id'),
|
|
240
|
+
url: getArg('url'),
|
|
241
|
+
timeout: parseInt(getArg('timeout') || '300') * 1000,
|
|
242
|
+
verbose: hasFlag('verbose') || hasFlag('v'),
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (options.platform !== 'custom' && !options.deploymentId && !options.projectName) {
|
|
246
|
+
console.error('Usage: node poll-deployment-status.js --platform <railway|cloudflare|vercel|custom>');
|
|
247
|
+
console.error(' --deployment-id <id> Deployment ID (Railway, Vercel)');
|
|
248
|
+
console.error(' --project-name <name> Project name (Cloudflare)');
|
|
249
|
+
console.error(' --timeout <seconds> Timeout in seconds (default: 300)');
|
|
250
|
+
console.error(' --verbose Show detailed output');
|
|
251
|
+
console.error(' --url <url> Custom status endpoint');
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const poller = new DeploymentPoller(options);
|
|
256
|
+
const result = await poller.poll();
|
|
257
|
+
process.exit(result.success ? 0 : 1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
main().catch(console.error);
|