github-delivery-os 1.0.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/src/install.js ADDED
@@ -0,0 +1,232 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+ const { execSync } = require('child_process');
4
+
5
+ const WORKFLOWS = [
6
+ 'sprint-child-creator',
7
+ 'auto-close-sprint',
8
+ 'notify-release-approver',
9
+ 'authorize-deployment',
10
+ 'auto-assign-qa',
11
+ 'telegram-issues',
12
+ 'setup-labels',
13
+ ];
14
+
15
+ const LABELS = [
16
+ ['intake', '0E8A16'],
17
+ ['bug', 'D93F0B'],
18
+ ['sprint', '1D76DB'],
19
+ ['sprint-active', '1D76DB'],
20
+ ['planning', '5319E7'],
21
+ ['sprint-planning', '5319E7'],
22
+ ['task', '7057FF'],
23
+ ['qa', 'FBCA04'],
24
+ ['qa-request', 'FBCA04'],
25
+ ['production', 'D93F0B'],
26
+ ['release', 'B60205'],
27
+ ['approval', '0E8A16'],
28
+ ['ready-for-deploy', '0E8A16'],
29
+ ['declined', 'B60205'],
30
+ ['risk', 'B60205'],
31
+ ];
32
+
33
+ function getPackageRoot() {
34
+ // When installed via npm, __dirname is node_modules/github-delivery-os/src
35
+ const possibleRoots = [
36
+ path.join(__dirname, '..'),
37
+ path.join(__dirname, '..', '..', '..'), // npx: node_modules/.bin/../../
38
+ ];
39
+ for (const root of possibleRoots) {
40
+ const workflowsPath = path.join(root, '.github', 'workflows', 'sprint-child-creator.yml');
41
+ if (fs.existsSync(workflowsPath)) {
42
+ return root;
43
+ }
44
+ }
45
+ throw new Error('Could not find package assets. Ensure .github/workflows exists.');
46
+ }
47
+
48
+ function runInstall(options) {
49
+ const {
50
+ targetDir = '.',
51
+ withTemplates = false,
52
+ withLabels = false,
53
+ overwrite = false,
54
+ dryRun = false,
55
+ } = options;
56
+
57
+ const pkgRoot = getPackageRoot();
58
+ const workflowsSrc = path.join(pkgRoot, '.github', 'workflows');
59
+ const templatesSrc = path.join(pkgRoot, '.github', 'ISSUE_TEMPLATE');
60
+ const targetAbs = path.resolve(process.cwd(), targetDir);
61
+
62
+ console.log('=== GitHub Delivery Operating System ===');
63
+ console.log(`Target: ${targetAbs}`);
64
+
65
+ if (overwrite) {
66
+ console.log('');
67
+ console.log('⚠️ WARNING: Overwrite mode — existing Delivery OS workflows/templates will be REPLACED.');
68
+ console.log(' (Your other workflows/templates with different names are not affected.)');
69
+ console.log('');
70
+ } else if (dryRun) {
71
+ console.log('Mode: dry-run (no files will be changed)');
72
+ console.log('');
73
+ } else {
74
+ console.log('Mode: skip-existing (existing workflows/templates will NOT be overwritten)');
75
+ console.log('');
76
+ }
77
+
78
+ // Ensure target structure
79
+ const workflowsDest = path.join(targetAbs, '.github', 'workflows');
80
+ const templatesDest = path.join(targetAbs, '.github', 'ISSUE_TEMPLATE');
81
+
82
+ if (!dryRun) {
83
+ fs.mkdirSync(workflowsDest, { recursive: true });
84
+ fs.mkdirSync(templatesDest, { recursive: true });
85
+ }
86
+
87
+ let workflowsCopied = 0;
88
+ let templatesCopied = 0;
89
+
90
+ // Copy workflows
91
+ for (const wf of WORKFLOWS) {
92
+ const src = path.join(workflowsSrc, `${wf}.yml`);
93
+ const dest = path.join(workflowsDest, `${wf}.yml`);
94
+
95
+ if (!fs.existsSync(src)) {
96
+ console.log(` Warning: source not found: ${wf}.yml`);
97
+ continue;
98
+ }
99
+
100
+ if (fs.existsSync(dest) && !overwrite) {
101
+ console.log(` Skipped (exists): ${wf}.yml`);
102
+ } else if (dryRun) {
103
+ console.log(` [dry-run] Would create: ${wf}.yml`);
104
+ workflowsCopied++;
105
+ } else {
106
+ fs.copyFileSync(src, dest);
107
+ console.log(` Created: ${wf}.yml`);
108
+ workflowsCopied++;
109
+ }
110
+ }
111
+
112
+ // Copy templates
113
+ if (withTemplates && fs.existsSync(templatesSrc)) {
114
+ const files = fs.readdirSync(templatesSrc);
115
+ for (const name of files) {
116
+ if (!name.endsWith('.yml') && !name.endsWith('.yaml')) continue;
117
+ const src = path.join(templatesSrc, name);
118
+ const dest = path.join(templatesDest, name);
119
+ if (!fs.statSync(src).isFile()) continue;
120
+
121
+ if (fs.existsSync(dest) && !overwrite) {
122
+ console.log(` Skipped (exists): ${name}`);
123
+ } else if (dryRun) {
124
+ console.log(` [dry-run] Would create template: ${name}`);
125
+ templatesCopied++;
126
+ } else {
127
+ fs.copyFileSync(src, dest);
128
+ console.log(` Created template: ${name}`);
129
+ templatesCopied++;
130
+ }
131
+ }
132
+ }
133
+
134
+ // Create labels via gh
135
+ let labelsCreated = 0;
136
+ let labelsSkipReason = '';
137
+
138
+ if (withLabels) {
139
+ if (dryRun) {
140
+ labelsSkipReason = 'Skipped in dry-run.';
141
+ console.log(' [dry-run] Labels would be created (skipped)');
142
+ } else {
143
+ try {
144
+ execSync('gh --version', { stdio: 'ignore' });
145
+ } catch {
146
+ labelsSkipReason = 'gh CLI not installed. Install from https://cli.github.com/';
147
+ console.log(` Skipped labels: ${labelsSkipReason}`);
148
+ }
149
+
150
+ if (!labelsSkipReason && !fs.existsSync(path.join(targetAbs, '.git'))) {
151
+ labelsSkipReason = 'Target is not a git repository.';
152
+ console.log(` Skipped labels: ${labelsSkipReason}`);
153
+ }
154
+
155
+ if (!labelsSkipReason) {
156
+ try {
157
+ execSync('gh auth status', { cwd: targetAbs, stdio: 'ignore' });
158
+ } catch {
159
+ labelsSkipReason = 'gh CLI not authenticated. Run: gh auth login';
160
+ console.log(` Skipped labels: ${labelsSkipReason}`);
161
+ }
162
+ }
163
+
164
+ if (!labelsSkipReason) {
165
+ try {
166
+ execSync('gh repo view', { cwd: targetAbs, stdio: 'ignore' });
167
+ } catch {
168
+ labelsSkipReason = 'Target repo not on GitHub or no push access.';
169
+ console.log(` Skipped labels: ${labelsSkipReason}`);
170
+ }
171
+ }
172
+
173
+ if (!labelsSkipReason) {
174
+ for (const [name, color] of LABELS) {
175
+ try {
176
+ execSync(`gh label create "${name}" --color "${color}"`, {
177
+ cwd: targetAbs,
178
+ stdio: 'pipe',
179
+ });
180
+ console.log(` Created label: ${name}`);
181
+ labelsCreated++;
182
+ } catch (err) {
183
+ const msg = err.stderr?.toString() || err.message || '';
184
+ if (/already exists/i.test(msg)) {
185
+ console.log(` Skipped (exists): ${name}`);
186
+ } else {
187
+ console.log(` Failed to create label '${name}': ${msg.trim()}`);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ }
193
+ }
194
+
195
+ // Summary
196
+ console.log('');
197
+ if (workflowsCopied > 0 || templatesCopied > 0 || labelsCreated > 0) {
198
+ if (dryRun) {
199
+ if (workflowsCopied > 0) console.log(`Would install ${workflowsCopied} workflow(s).`);
200
+ if (templatesCopied > 0) console.log(`Would copy ${templatesCopied} issue template(s).`);
201
+ } else {
202
+ if (workflowsCopied > 0) console.log(`Installed ${workflowsCopied} workflow(s).`);
203
+ if (templatesCopied > 0) console.log(`Copied ${templatesCopied} issue template(s).`);
204
+ if (labelsCreated > 0) console.log(`Created ${labelsCreated} label(s).`);
205
+ }
206
+ console.log('');
207
+ console.log('Next steps:');
208
+ console.log(' 1. Create labels: Actions → Setup Labels → Run workflow');
209
+ if (labelsSkipReason) console.log(` (Labels skipped: ${labelsSkipReason})`);
210
+ console.log(' 2. Configure repo variables (Settings → Secrets and variables → Actions):');
211
+ console.log(' - RELEASE_APPROVER: GitHub username of release approver');
212
+ console.log(' - QA_APPROVER: GitHub username of QA approver');
213
+ console.log(' - QA_ASSIGNEES: Comma-separated usernames for QA assignment');
214
+ console.log(' 3. Add secrets (optional, for Telegram): TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID');
215
+ if (!withTemplates) {
216
+ console.log(' 4. Copy templates: re-run with --with-templates');
217
+ }
218
+ console.log('');
219
+ console.log('See https://jkaweesi22.github.io/github-delivery-operating-system/ for full docs.');
220
+ } else {
221
+ if (dryRun) {
222
+ console.log('Dry run complete. No files were changed.');
223
+ } else {
224
+ console.log('No new files created (existing files were skipped).');
225
+ console.log('To update: use --overwrite (run with --dry-run first to preview).');
226
+ }
227
+ }
228
+ console.log('');
229
+ console.log('=== Installation complete ===');
230
+ }
231
+
232
+ module.exports = { runInstall };