@justanarthur/just-github-actions-n-workflows-cli 0.0.0-beta.12 → 0.0.0-beta.13

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.
@@ -3,6 +3,7 @@ import { checkbox, select } from "@inquirer/prompts";
3
3
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { join } from "node:path";
5
5
  import { REPO, enrichWorkflows, fetchTags, fetchWorkflowContent, fetchWorkflowList, } from "../github.js";
6
+ import { injectRefComment, mergeLockfile, readLockfile, writeLockfile, } from "../lockfile.js";
6
7
  export default class Init extends Command {
7
8
  static description = "Scaffold workflow files into .github/workflows/ of the current repo";
8
9
  static examples = [
@@ -128,6 +129,7 @@ export default class Init extends Command {
128
129
  this.log(ux.colorize("dim", ` fetching from ${REPO} @ ${ref}\n`));
129
130
  let created = 0;
130
131
  let skipped = 0;
132
+ const installed = [];
131
133
  for (const workflow of selected) {
132
134
  const targetPath = join(targetDir, workflow.file);
133
135
  if (!flags.force && existsSync(targetPath)) {
@@ -136,15 +138,24 @@ export default class Init extends Command {
136
138
  continue;
137
139
  }
138
140
  try {
139
- const content = await fetchWorkflowContent(workflow.file, ref);
141
+ let content = await fetchWorkflowContent(workflow.file, ref);
142
+ content = injectRefComment(content, ref);
140
143
  writeFileSync(targetPath, content, "utf-8");
141
144
  this.log(` ${ux.colorize("green", "create")} .github/workflows/${workflow.file}`);
145
+ installed.push({ name: workflow.name, file: workflow.file });
142
146
  created++;
143
147
  }
144
148
  catch (error) {
145
149
  this.log(` ${ux.colorize("red", "error")} ${workflow.file}: ${error.message}`);
146
150
  }
147
151
  }
152
+ // --- write lock file ---
153
+ if (installed.length > 0) {
154
+ const existing = readLockfile();
155
+ const lock = mergeLockfile(existing, ref, installed);
156
+ writeLockfile(lock);
157
+ this.log(ux.colorize("dim", `\n lock file written → .github/workflows/.toolkit-lock.json`));
158
+ }
148
159
  return { created, skipped };
149
160
  }
150
161
  // ── step 4: secrets reminder ───────────────────────────
@@ -0,0 +1,53 @@
1
+ // cli/src/commands/status.ts
2
+ // ---
3
+ // shows the current state of installed workflows:
4
+ // version, install date, and whether each is outdated.
5
+ // ---
6
+ import { Command, Flags, ux } from "@oclif/core";
7
+ import { fetchLatestTag } from "../github.js";
8
+ import { readLockfile } from "../lockfile.js";
9
+ export default class Status extends Command {
10
+ static description = "Show the status of installed workflows";
11
+ static examples = [
12
+ "<%= config.bin %> status",
13
+ "<%= config.bin %> status --ref v2.0.0",
14
+ ];
15
+ static flags = {
16
+ ref: Flags.string({
17
+ description: "Compare against this ref instead of the latest tag",
18
+ }),
19
+ };
20
+ async run() {
21
+ const { flags } = await this.parse(Status);
22
+ const lock = readLockfile();
23
+ if (!lock || Object.keys(lock.workflows).length === 0) {
24
+ this.log(ux.colorize("yellow", "\n no workflows installed — run `init` first.\n"));
25
+ return;
26
+ }
27
+ const targetRef = flags.ref ?? await fetchLatestTag();
28
+ const entries = Object.values(lock.workflows);
29
+ this.log();
30
+ this.log(ux.colorize("bold", " installed workflows"));
31
+ this.log(ux.colorize("dim", ` latest: ${targetRef}\n`));
32
+ let outdatedCount = 0;
33
+ for (const entry of entries) {
34
+ const current = entry.ref === targetRef;
35
+ const icon = current ? ux.colorize("green", "✓") : ux.colorize("yellow", "⬆");
36
+ const refLabel = current
37
+ ? ux.colorize("green", entry.ref)
38
+ : `${ux.colorize("yellow", entry.ref)} → ${ux.colorize("green", targetRef)}`;
39
+ const date = ux.colorize("dim", new Date(entry.installedAt).toLocaleDateString());
40
+ this.log(` ${icon} ${ux.colorize("cyan", entry.name.padEnd(28))} ${refLabel.padEnd(50)} ${date}`);
41
+ if (!current)
42
+ outdatedCount++;
43
+ }
44
+ this.log();
45
+ if (outdatedCount > 0) {
46
+ this.log(ux.colorize("yellow", ` ${outdatedCount} workflow(s) can be updated — run \`update\` to upgrade.\n`));
47
+ }
48
+ else {
49
+ this.log(ux.colorize("green", ` all ${entries.length} workflow(s) are up to date.\n`));
50
+ }
51
+ }
52
+ }
53
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1,108 @@
1
+ // cli/src/commands/update.ts
2
+ // ---
3
+ // updates previously installed workflows to a newer version.
4
+ // reads .toolkit-lock.json to find installed workflows, then
5
+ // re-fetches them from the specified (or latest) git ref.
6
+ // ---
7
+ import { Command, Flags, ux } from "@oclif/core";
8
+ import { confirm } from "@inquirer/prompts";
9
+ import { writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+ import { REPO, fetchLatestTag, fetchWorkflowContent, } from "../github.js";
12
+ import { injectRefComment, mergeLockfile, readLockfile, writeLockfile, } from "../lockfile.js";
13
+ export default class Update extends Command {
14
+ static description = "Update installed workflows to a newer version";
15
+ static examples = [
16
+ "<%= config.bin %> update",
17
+ "<%= config.bin %> update --ref v2.0.0",
18
+ "<%= config.bin %> update --yes",
19
+ ];
20
+ static flags = {
21
+ ref: Flags.string({
22
+ description: "Target git ref to update to (branch, tag, or sha)",
23
+ }),
24
+ yes: Flags.boolean({
25
+ char: "y",
26
+ description: "Skip confirmation prompt",
27
+ default: false,
28
+ }),
29
+ };
30
+ async run() {
31
+ const { flags } = await this.parse(Update);
32
+ // --- read lock file ---
33
+ const lock = readLockfile();
34
+ if (!lock || Object.keys(lock.workflows).length === 0) {
35
+ this.error("No .toolkit-lock.json found. Run `init` first to install workflows.", { exit: 1 });
36
+ }
37
+ // --- resolve target ref ---
38
+ const targetRef = flags.ref ?? await fetchLatestTag();
39
+ const entries = Object.values(lock.workflows);
40
+ this.log();
41
+ this.log(ux.colorize("bold", " workflow update check"));
42
+ this.log(ux.colorize("dim", ` target: ${targetRef}\n`));
43
+ // --- compare versions ---
44
+ const outdated = [];
45
+ const upToDate = [];
46
+ for (const entry of entries) {
47
+ if (entry.ref === targetRef) {
48
+ upToDate.push(entry);
49
+ }
50
+ else {
51
+ outdated.push(entry);
52
+ }
53
+ }
54
+ // --- print status table ---
55
+ for (const entry of entries) {
56
+ const current = entry.ref === targetRef;
57
+ const icon = current ? ux.colorize("green", "✓") : ux.colorize("yellow", "⬆");
58
+ const refLabel = current
59
+ ? ux.colorize("green", entry.ref)
60
+ : `${ux.colorize("yellow", entry.ref)} → ${ux.colorize("green", targetRef)}`;
61
+ this.log(` ${icon} ${ux.colorize("cyan", entry.name.padEnd(28))} ${refLabel}`);
62
+ }
63
+ this.log();
64
+ if (outdated.length === 0) {
65
+ this.log(ux.colorize("green", ` all ${entries.length} workflow(s) are up to date at ${targetRef}\n`));
66
+ return;
67
+ }
68
+ // --- confirm ---
69
+ if (!flags.yes) {
70
+ const proceed = await confirm({
71
+ message: `Update ${outdated.length} workflow(s) to ${targetRef}?`,
72
+ default: true,
73
+ });
74
+ if (!proceed) {
75
+ this.log(ux.colorize("yellow", "\n cancelled.\n"));
76
+ return;
77
+ }
78
+ this.log();
79
+ }
80
+ // --- update files ---
81
+ const targetDir = join(process.cwd(), ".github", "workflows");
82
+ let updated = 0;
83
+ let errors = 0;
84
+ const updatedEntries = [];
85
+ this.log(ux.colorize("dim", ` fetching from ${REPO} @ ${targetRef}\n`));
86
+ for (const entry of outdated) {
87
+ try {
88
+ let content = await fetchWorkflowContent(entry.file, targetRef);
89
+ content = injectRefComment(content, targetRef);
90
+ writeFileSync(join(targetDir, entry.file), content, "utf-8");
91
+ this.log(` ${ux.colorize("green", "update")} ${entry.file} ${ux.colorize("dim", `(${entry.ref} → ${targetRef})`)}`);
92
+ updatedEntries.push({ name: entry.name, file: entry.file });
93
+ updated++;
94
+ }
95
+ catch (error) {
96
+ this.log(` ${ux.colorize("red", "error")} ${entry.file}: ${error.message}`);
97
+ errors++;
98
+ }
99
+ }
100
+ // --- update lock file ---
101
+ if (updatedEntries.length > 0) {
102
+ const updatedLock = mergeLockfile(lock, targetRef, updatedEntries);
103
+ writeLockfile(updatedLock);
104
+ }
105
+ this.log(`\n done — ${ux.colorize("green", `${updated} updated`)}, ${errors} errors, ${upToDate.length} already current\n`);
106
+ }
107
+ }
108
+ //# sourceMappingURL=update.js.map
package/dist/github.js CHANGED
@@ -49,6 +49,12 @@ export async function fetchTags() {
49
49
  const tags = await res.json();
50
50
  return tags.map((t) => t.name);
51
51
  }
52
+ // --- fetchLatestTag ---
53
+ // returns the most recent tag, or "main" if no tags exist.
54
+ export async function fetchLatestTag() {
55
+ const tags = await fetchTags();
56
+ return tags.length > 0 ? tags[0] : "main";
57
+ }
52
58
  export async function fetchWorkflowList(gitRef) {
53
59
  const url = `${API_BASE}/contents/workflows?ref=${gitRef}`;
54
60
  const res = await fetch(url, {
@@ -0,0 +1,72 @@
1
+ // cli/src/lockfile.ts
2
+ // ---
3
+ // reads and writes .github/workflows/.toolkit-lock.json
4
+ // tracks which version (git ref) each workflow was installed from.
5
+ // ---
6
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
7
+ import { join } from "node:path";
8
+ // --- constants ---
9
+ const LOCKFILE_NAME = ".toolkit-lock.json";
10
+ export function lockfilePath() {
11
+ return join(process.cwd(), ".github", "workflows", LOCKFILE_NAME);
12
+ }
13
+ // --- read ---
14
+ export function readLockfile() {
15
+ const path = lockfilePath();
16
+ if (!existsSync(path))
17
+ return null;
18
+ try {
19
+ const raw = readFileSync(path, "utf-8");
20
+ return JSON.parse(raw);
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ // --- write ---
27
+ export function writeLockfile(lock) {
28
+ const path = lockfilePath();
29
+ writeFileSync(path, JSON.stringify(lock, null, 2) + "\n", "utf-8");
30
+ }
31
+ // --- merge ---
32
+ // merges new entries into an existing lockfile (or creates a new one).
33
+ export function mergeLockfile(existing, ref, entries) {
34
+ const now = new Date().toISOString();
35
+ const lock = existing ?? {
36
+ ref,
37
+ installedAt: now,
38
+ workflows: {},
39
+ };
40
+ lock.ref = ref;
41
+ lock.installedAt = now;
42
+ for (const entry of entries) {
43
+ lock.workflows[entry.file] = {
44
+ name: entry.name,
45
+ file: entry.file,
46
+ ref,
47
+ installedAt: now,
48
+ };
49
+ }
50
+ return lock;
51
+ }
52
+ // --- version comment ---
53
+ // prepends a `# toolkit-ref: <ref>` comment to workflow content.
54
+ export function injectRefComment(content, ref) {
55
+ const marker = "# toolkit-ref:";
56
+ const comment = `${marker} ${ref}`;
57
+ // replace existing marker if present
58
+ if (content.includes(marker)) {
59
+ return content.replace(/^# toolkit-ref:.*$/m, comment);
60
+ }
61
+ // prepend before first non-comment, non-empty line
62
+ const lines = content.split("\n");
63
+ const insertIdx = lines.findIndex((l) => l.trim() !== "" && !l.startsWith("#"));
64
+ if (insertIdx >= 0) {
65
+ lines.splice(insertIdx, 0, comment);
66
+ }
67
+ else {
68
+ lines.push(comment);
69
+ }
70
+ return lines.join("\n");
71
+ }
72
+ //# sourceMappingURL=lockfile.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justanarthur/just-github-actions-n-workflows-cli",
3
- "version": "0.0.0-beta.12",
3
+ "version": "0.0.0-beta.13",
4
4
  "description": "Release automation toolkit — version bumping, npm publishing, docker publishing, VPS deployment via GitHub Actions composite actions",
5
5
  "type": "module",
6
6
  "bin": {