@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.
- package/dist/commands/init.js +12 -1
- package/dist/commands/status.js +53 -0
- package/dist/commands/update.js +108 -0
- package/dist/github.js +6 -0
- package/dist/lockfile.js +72 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
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, {
|
package/dist/lockfile.js
ADDED
|
@@ -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.
|
|
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": {
|