copilot-tap-extension 2.0.2 → 2.0.5

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/bin/install.mjs CHANGED
@@ -47,37 +47,38 @@ Installs:
47
47
  `);
48
48
  }
49
49
 
50
+ const OPTION_ACTIONS = new Map([
51
+ ["--global", (flags) => { flags.scope = "global"; }],
52
+ ["-g", (flags) => { flags.scope = "global"; }],
53
+ ["--local", (flags) => { flags.scope = "local"; }],
54
+ ["-l", (flags) => { flags.scope = "local"; }],
55
+ ["--force", (flags) => { flags.force = true; }],
56
+ ["-f", (flags) => { flags.force = true; }],
57
+ ["--full", (flags) => { flags.force = true; }],
58
+ // Keep legacy flags working as no-ops.
59
+ ["--update", () => {}],
60
+ ["-u", () => {}],
61
+ ["--help", (flags) => { flags.help = true; }],
62
+ ["-h", (flags) => { flags.help = true; }]
63
+ ]);
64
+
65
+ function applyOption(flags, arg) {
66
+ const action = OPTION_ACTIONS.get(arg);
67
+ if (action) {
68
+ action(flags);
69
+ return;
70
+ }
71
+
72
+ console.error(`Unknown option: ${arg}`);
73
+ usage();
74
+ process.exit(1);
75
+ }
76
+
50
77
  function parseArgs(argv) {
51
78
  const args = argv.slice(2);
52
79
  const flags = { scope: "global", force: false, help: false };
53
80
  for (const arg of args) {
54
- switch (arg) {
55
- case "--global":
56
- case "-g":
57
- flags.scope = "global";
58
- break;
59
- case "--local":
60
- case "-l":
61
- flags.scope = "local";
62
- break;
63
- case "--force":
64
- case "-f":
65
- case "--full":
66
- flags.force = true;
67
- break;
68
- // Keep legacy flags working
69
- case "--update":
70
- case "-u":
71
- break;
72
- case "--help":
73
- case "-h":
74
- flags.help = true;
75
- break;
76
- default:
77
- console.error(`Unknown option: ${arg}`);
78
- usage();
79
- process.exit(1);
80
- }
81
+ applyOption(flags, arg);
81
82
  }
82
83
  return flags;
83
84
  }
@@ -131,66 +132,107 @@ function isCopilotCliInstalled() {
131
132
 
132
133
  function removeDeprecatedSkills(targetRoot) {
133
134
  const deprecated = ["loop", "monitor", "create-provider"];
134
- let allOk = true;
135
- let removedAny = false;
135
+ const state = { allOk: true, removedAny: false };
136
136
 
137
137
  for (const name of deprecated) {
138
- const oldPath = path.join(targetRoot, "skills", name, "SKILL.md");
139
- if (!existsSync(oldPath)) {
140
- continue;
141
- }
142
- try {
143
- unlinkSync(oldPath);
144
- if (!removedAny) {
145
- console.log();
146
- removedAny = true;
147
- }
148
- console.log(` ✓ Removed deprecated skill: skills/${name}/SKILL.md`);
149
- } catch {
150
- allOk = false;
151
- console.warn(` ⚠ Could not remove deprecated skill at ${oldPath} — remove it manually`);
152
- }
138
+ applyDeprecatedSkillRemoval(targetRoot, name, state);
153
139
  }
154
140
 
155
- if (removedAny) {
141
+ if (state.removedAny) {
156
142
  console.log(`\n Use the new namespaced commands: /tap-loop /tap-monitor /tap-create-provider`);
157
143
  }
158
144
 
159
- return allOk;
145
+ return state.allOk;
160
146
  }
161
147
 
162
- function install(flags) {
163
- const targetRoot = getTargetRoot(flags.scope);
164
- const scopeLabel = flags.scope === "global" ? "global (~/.copilot)" : "local (.github)";
165
- const packageVersion = getPackageVersion();
148
+ function applyDeprecatedSkillRemoval(targetRoot, name, state) {
149
+ const result = removeDeprecatedSkill(targetRoot, name);
150
+ if (result.removed) {
151
+ logDeprecatedSkillRemoved(name, state);
152
+ }
153
+ if (!result.ok) {
154
+ state.allOk = false;
155
+ }
156
+ }
157
+
158
+ function logDeprecatedSkillRemoved(name, state) {
159
+ if (!state.removedAny) {
160
+ console.log();
161
+ state.removedAny = true;
162
+ }
163
+ console.log(` ✓ Removed deprecated skill: skills/${name}/SKILL.md`);
164
+ }
165
+
166
+ function removeDeprecatedSkill(targetRoot, name) {
167
+ const oldPath = path.join(targetRoot, "skills", name, "SKILL.md");
168
+ if (!existsSync(oldPath)) {
169
+ return { ok: true, removed: false };
170
+ }
171
+
172
+ try {
173
+ unlinkSync(oldPath);
174
+ return { ok: true, removed: true };
175
+ } catch {
176
+ console.warn(` ⚠ Could not remove deprecated skill at ${oldPath} — remove it manually`);
177
+ return { ok: false, removed: false };
178
+ }
179
+ }
166
180
 
167
- if (flags.scope === "global" && !isCopilotCliInstalled()) {
168
- console.log(`\n⚠ Copilot CLI does not appear to be installed.`);
169
- console.log(` Install it first: https://docs.github.com/en/copilot/github-copilot-in-the-cli`);
170
- console.log(` Then re-run: npx copilot-tap-extension\n`);
171
- process.exit(1);
181
+ function getScopeLabel(scope) {
182
+ return scope === "global" ? "global (~/.copilot)" : "local (.github)";
183
+ }
184
+
185
+ function ensureGlobalInstallSupported(scope) {
186
+ if (scope !== "global" || isCopilotCliInstalled()) {
187
+ return;
172
188
  }
173
189
 
190
+ console.log(`\n⚠ Copilot CLI does not appear to be installed.`);
191
+ console.log(` Install it first: https://docs.github.com/en/copilot/github-copilot-in-the-cli`);
192
+ console.log(` Then re-run: npx copilot-tap-extension\n`);
193
+ process.exit(1);
194
+ }
195
+
196
+ function getInstallState(targetRoot, flags) {
174
197
  const installed = isAlreadyInstalled(targetRoot);
175
198
  const isUpdate = installed && !flags.force;
176
199
  const isReinstall = installed && flags.force;
177
200
  const installedVersion = installed ? getInstalledVersion(targetRoot) : null;
178
201
 
179
- if (isUpdate) {
180
- if (installedVersion && installedVersion === packageVersion) {
181
- console.log(`\n${BRAND} — already up to date (v${installedVersion})\n`);
182
- process.exit(0);
183
- }
184
- const fromLabel = installedVersion ? `v${installedVersion}` : "unknown";
202
+ return { installed, isUpdate, isReinstall, installedVersion };
203
+ }
204
+
205
+ function exitIfAlreadyCurrent(state, packageVersion) {
206
+ if (!state.isUpdate || !state.installedVersion || state.installedVersion !== packageVersion) {
207
+ return;
208
+ }
209
+
210
+ console.log(`\n${BRAND} — already up to date (v${state.installedVersion})\n`);
211
+ process.exit(0);
212
+ }
213
+
214
+ function getVersionLabel(version) {
215
+ return version ? `v${version}` : "unknown";
216
+ }
217
+
218
+ function announceInstall(state, packageVersion, scopeLabel) {
219
+ if (state.isUpdate) {
220
+ const fromLabel = getVersionLabel(state.installedVersion);
185
221
  console.log(`\n${BRAND} — updating ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
186
- } else if (isReinstall) {
187
- const fromLabel = installedVersion ? `v${installedVersion}` : "unknown";
222
+ return;
223
+ }
224
+
225
+ if (state.isReinstall) {
226
+ const fromLabel = getVersionLabel(state.installedVersion);
188
227
  console.log(`\n${BRAND} — reinstalling ${fromLabel} → v${packageVersion} (${scopeLabel})\n`);
189
- } else {
190
- console.log(`\n${BRAND} — installing v${packageVersion} (${scopeLabel})\n`);
228
+ return;
191
229
  }
192
230
 
193
- const coreArtifacts = [
231
+ console.log(`\n${BRAND} installing v${packageVersion} (${scopeLabel})\n`);
232
+ }
233
+
234
+ function buildCoreArtifacts(targetRoot) {
235
+ return [
194
236
  {
195
237
  src: path.join(distDir, "extension.mjs"),
196
238
  dest: path.join(targetRoot, "extensions", EXT_DIR_NAME, "extension.mjs"),
@@ -202,8 +244,10 @@ function install(flags) {
202
244
  label: "extensions/tap/version.json"
203
245
  }
204
246
  ];
247
+ }
205
248
 
206
- const ancillaryArtifacts = [
249
+ function buildAncillaryArtifacts(targetRoot) {
250
+ return [
207
251
  {
208
252
  src: path.join(distDir, "skills", "tap-loop", "SKILL.md"),
209
253
  dest: path.join(targetRoot, "skills", "tap-loop", "SKILL.md"),
@@ -230,37 +274,69 @@ function install(flags) {
230
274
  label: "copilot-instructions.md"
231
275
  }
232
276
  ];
277
+ }
233
278
 
279
+ function buildInstallArtifacts(targetRoot, isUpdate) {
280
+ const coreArtifacts = buildCoreArtifacts(targetRoot);
281
+ const ancillaryArtifacts = buildAncillaryArtifacts(targetRoot);
234
282
  // During updates, also install ancillary artifacts that don't yet exist at the destination
235
283
  // (e.g. new skills added in a newer version). Existing ones are preserved to keep user customizations.
236
284
  const newAncillaryArtifacts = isUpdate
237
285
  ? ancillaryArtifacts.filter(({ dest }) => !existsSync(dest))
238
286
  : ancillaryArtifacts;
239
- const artifacts = [...coreArtifacts, ...newAncillaryArtifacts];
287
+ return [...coreArtifacts, ...newAncillaryArtifacts];
288
+ }
240
289
 
290
+ function copyArtifacts(artifacts) {
241
291
  let allOk = true;
242
292
  for (const { src, dest, label } of artifacts) {
243
293
  if (!copyArtifact(src, dest, label)) {
244
294
  allOk = false;
245
295
  }
246
296
  }
297
+ return allOk;
298
+ }
247
299
 
248
- if (installed && !removeDeprecatedSkills(targetRoot)) {
249
- allOk = false;
300
+ function getInstallVerb(state) {
301
+ if (state.isUpdate) {
302
+ return "updated";
250
303
  }
304
+ return state.isReinstall ? "reinstalled" : "installed";
305
+ }
251
306
 
307
+ function finishInstall(allOk, state, targetRoot) {
252
308
  console.log();
309
+ const verb = getInstallVerb(state);
253
310
  if (allOk) {
254
- const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
255
311
  console.log(`✓ ${BRAND} ${verb} to ${targetRoot}`);
256
312
  return;
257
313
  }
258
314
 
259
- const verb = isUpdate ? "updated" : isReinstall ? "reinstalled" : "installed";
260
315
  console.error(`⚠ Some artifacts could not be ${verb}.`);
261
316
  process.exit(1);
262
317
  }
263
318
 
319
+ function install(flags) {
320
+ const targetRoot = getTargetRoot(flags.scope);
321
+ const scopeLabel = getScopeLabel(flags.scope);
322
+ const packageVersion = getPackageVersion();
323
+
324
+ ensureGlobalInstallSupported(flags.scope);
325
+
326
+ const state = getInstallState(targetRoot, flags);
327
+ exitIfAlreadyCurrent(state, packageVersion);
328
+ announceInstall(state, packageVersion, scopeLabel);
329
+
330
+ const artifacts = buildInstallArtifacts(targetRoot, state.isUpdate);
331
+ let allOk = copyArtifacts(artifacts);
332
+
333
+ if (state.installed && !removeDeprecatedSkills(targetRoot)) {
334
+ allOk = false;
335
+ }
336
+
337
+ finishInstall(allOk, state, targetRoot);
338
+ }
339
+
264
340
  const flags = parseArgs(process.argv);
265
341
 
266
342
  if (flags.help) {