@actuate-media/cli 0.1.0 → 0.1.2
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/.turbo/turbo-build.log +2 -1
- package/CHANGELOG.md +44 -0
- package/LICENSE +21 -0
- package/dist/commands/export.js +1 -1
- package/dist/commands/export.js.map +1 -1
- package/dist/commands/generate.js +1 -1
- package/dist/commands/generate.js.map +1 -1
- package/dist/commands/import.js +1 -1
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/seed.js +1 -1
- package/dist/commands/seed.js.map +1 -1
- package/dist/commands/update-check.js +2 -2
- package/dist/commands/update-check.js.map +1 -1
- package/dist/commands/upgrade.js +4 -4
- package/dist/commands/upgrade.js.map +1 -1
- package/dist/index.js +0 -0
- package/package.json +33 -33
- package/src/commands/export.ts +131 -131
- package/src/commands/generate.ts +28 -28
- package/src/commands/import.ts +243 -243
- package/src/commands/migrate.ts +62 -62
- package/src/commands/seed.ts +388 -388
- package/src/commands/update-check.ts +147 -147
- package/src/commands/upgrade.ts +173 -173
- package/src/index.ts +26 -26
- package/src/utils/logger.ts +26 -26
- package/tsconfig.json +9 -9
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile } from "node:fs/promises";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import ora from "ora";
|
|
6
|
-
import { logger } from "../utils/logger.js";
|
|
7
|
-
|
|
8
|
-
const UPDATE_SERVER_URL = "https://updates.actuatecms.com/api/versions";
|
|
9
|
-
|
|
10
|
-
function compareVersions(a: string, b: string): number {
|
|
11
|
-
const pa = a.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
12
|
-
const pb = b.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
13
|
-
for (let i = 0; i < 3; i++) {
|
|
14
|
-
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
15
|
-
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
16
|
-
}
|
|
17
|
-
return 0;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface PackageVersions {
|
|
21
|
-
[pkg: string]: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
async function getLocalVersions(): Promise<PackageVersions> {
|
|
25
|
-
const pkgPath = resolve(process.cwd(), "package.json");
|
|
26
|
-
if (!existsSync(pkgPath)) {
|
|
27
|
-
throw new Error("No package.json found in the current directory.");
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const raw = await readFile(pkgPath, "utf-8");
|
|
31
|
-
const pkg = JSON.parse(raw);
|
|
32
|
-
const versions: PackageVersions = {};
|
|
33
|
-
|
|
34
|
-
for (const section of ["dependencies", "devDependencies"] as const) {
|
|
35
|
-
const deps = pkg[section];
|
|
36
|
-
if (!deps) continue;
|
|
37
|
-
for (const [name, version] of Object.entries(deps)) {
|
|
38
|
-
if (name.startsWith("@actuate-media/") && typeof version === "string") {
|
|
39
|
-
versions[name] = version.replace(/^[\^~]/, "").replace("workspace:", "");
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return versions;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
async function fetchLatestVersions(
|
|
48
|
-
packages: string[],
|
|
49
|
-
): Promise<PackageVersions> {
|
|
50
|
-
const versions: PackageVersions = {};
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
const res = await fetch(UPDATE_SERVER_URL);
|
|
54
|
-
if (res.ok) {
|
|
55
|
-
const data = await res.json();
|
|
56
|
-
for (const pkg of packages) {
|
|
57
|
-
if (data[pkg]) {
|
|
58
|
-
versions[pkg] = data[pkg];
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
if (Object.keys(versions).length === packages.length) {
|
|
62
|
-
return versions;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
} catch {
|
|
66
|
-
// Fall through to npm registry
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
for (const pkg of packages) {
|
|
70
|
-
if (versions[pkg]) continue;
|
|
71
|
-
try {
|
|
72
|
-
const npmUrl = `https://registry.npmjs.org/${pkg}/latest`;
|
|
73
|
-
const res = await fetch(npmUrl);
|
|
74
|
-
if (res.ok) {
|
|
75
|
-
const data = await res.json();
|
|
76
|
-
versions[pkg] = data.version;
|
|
77
|
-
}
|
|
78
|
-
} catch {
|
|
79
|
-
// Skip packages we can't look up
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return versions;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
async function runUpdateCheck(): Promise<void> {
|
|
87
|
-
const spinner = ora("Checking for updates…").start();
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const local = await getLocalVersions();
|
|
91
|
-
const packages = Object.keys(local);
|
|
92
|
-
|
|
93
|
-
if (packages.length === 0) {
|
|
94
|
-
spinner.warn("No @actuate-media/* packages found in package.json.");
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const latest = await fetchLatestVersions(packages);
|
|
99
|
-
spinner.stop();
|
|
100
|
-
|
|
101
|
-
let hasUpdates = false;
|
|
102
|
-
|
|
103
|
-
console.log("\n " + "Package".padEnd(30) + "Current".padEnd(14) + "Latest".padEnd(14) + "Status");
|
|
104
|
-
console.log(" " + "-".repeat(68));
|
|
105
|
-
|
|
106
|
-
for (const pkg of packages) {
|
|
107
|
-
const current = local[pkg];
|
|
108
|
-
const remote = latest[pkg] ?? "unknown";
|
|
109
|
-
let status: string;
|
|
110
|
-
|
|
111
|
-
if (remote === "unknown") {
|
|
112
|
-
status = "?";
|
|
113
|
-
} else if (compareVersions(current!, remote) < 0) {
|
|
114
|
-
status = "Update available";
|
|
115
|
-
hasUpdates = true;
|
|
116
|
-
} else {
|
|
117
|
-
status = "Up to date";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
console.log(
|
|
121
|
-
` ${pkg.padEnd(30)}${(current ?? '').padEnd(14)}${remote.padEnd(14)}${status}`,
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
console.log();
|
|
126
|
-
|
|
127
|
-
if (hasUpdates) {
|
|
128
|
-
logger.warn('Updates available! Run "actuate upgrade" to update.');
|
|
129
|
-
} else {
|
|
130
|
-
logger.success("All packages are up to date!");
|
|
131
|
-
}
|
|
132
|
-
} catch (err) {
|
|
133
|
-
spinner.fail("Update check failed.");
|
|
134
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
-
logger.error(message);
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export function registerUpdateCheckCommand(program: Command): void {
|
|
141
|
-
program
|
|
142
|
-
.command("update-check")
|
|
143
|
-
.description(
|
|
144
|
-
"Check for available Actuate CMS package updates",
|
|
145
|
-
)
|
|
146
|
-
.action(runUpdateCheck);
|
|
147
|
-
}
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import ora from "ora";
|
|
6
|
+
import { logger } from "../utils/logger.js";
|
|
7
|
+
|
|
8
|
+
const UPDATE_SERVER_URL = "https://updates.actuatecms.com/api/versions";
|
|
9
|
+
|
|
10
|
+
function compareVersions(a: string, b: string): number {
|
|
11
|
+
const pa = a.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
12
|
+
const pb = b.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
13
|
+
for (let i = 0; i < 3; i++) {
|
|
14
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
15
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
16
|
+
}
|
|
17
|
+
return 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface PackageVersions {
|
|
21
|
+
[pkg: string]: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function getLocalVersions(): Promise<PackageVersions> {
|
|
25
|
+
const pkgPath = resolve(process.cwd(), "package.json");
|
|
26
|
+
if (!existsSync(pkgPath)) {
|
|
27
|
+
throw new Error("No package.json found in the current directory.");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const raw = await readFile(pkgPath, "utf-8");
|
|
31
|
+
const pkg = JSON.parse(raw);
|
|
32
|
+
const versions: PackageVersions = {};
|
|
33
|
+
|
|
34
|
+
for (const section of ["dependencies", "devDependencies"] as const) {
|
|
35
|
+
const deps = pkg[section];
|
|
36
|
+
if (!deps) continue;
|
|
37
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
38
|
+
if (name.startsWith("@actuate-media/") && typeof version === "string") {
|
|
39
|
+
versions[name] = version.replace(/^[\^~]/, "").replace("workspace:", "");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return versions;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function fetchLatestVersions(
|
|
48
|
+
packages: string[],
|
|
49
|
+
): Promise<PackageVersions> {
|
|
50
|
+
const versions: PackageVersions = {};
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const res = await fetch(UPDATE_SERVER_URL);
|
|
54
|
+
if (res.ok) {
|
|
55
|
+
const data = await res.json();
|
|
56
|
+
for (const pkg of packages) {
|
|
57
|
+
if (data[pkg]) {
|
|
58
|
+
versions[pkg] = data[pkg];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (Object.keys(versions).length === packages.length) {
|
|
62
|
+
return versions;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
// Fall through to npm registry
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (const pkg of packages) {
|
|
70
|
+
if (versions[pkg]) continue;
|
|
71
|
+
try {
|
|
72
|
+
const npmUrl = `https://registry.npmjs.org/${pkg}/latest`;
|
|
73
|
+
const res = await fetch(npmUrl);
|
|
74
|
+
if (res.ok) {
|
|
75
|
+
const data = await res.json();
|
|
76
|
+
versions[pkg] = data.version;
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
// Skip packages we can't look up
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return versions;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function runUpdateCheck(): Promise<void> {
|
|
87
|
+
const spinner = ora("Checking for updates…").start();
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const local = await getLocalVersions();
|
|
91
|
+
const packages = Object.keys(local);
|
|
92
|
+
|
|
93
|
+
if (packages.length === 0) {
|
|
94
|
+
spinner.warn("No @actuate-media/* packages found in package.json.");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const latest = await fetchLatestVersions(packages);
|
|
99
|
+
spinner.stop();
|
|
100
|
+
|
|
101
|
+
let hasUpdates = false;
|
|
102
|
+
|
|
103
|
+
console.log("\n " + "Package".padEnd(30) + "Current".padEnd(14) + "Latest".padEnd(14) + "Status");
|
|
104
|
+
console.log(" " + "-".repeat(68));
|
|
105
|
+
|
|
106
|
+
for (const pkg of packages) {
|
|
107
|
+
const current = local[pkg];
|
|
108
|
+
const remote = latest[pkg] ?? "unknown";
|
|
109
|
+
let status: string;
|
|
110
|
+
|
|
111
|
+
if (remote === "unknown") {
|
|
112
|
+
status = "?";
|
|
113
|
+
} else if (compareVersions(current!, remote) < 0) {
|
|
114
|
+
status = "Update available";
|
|
115
|
+
hasUpdates = true;
|
|
116
|
+
} else {
|
|
117
|
+
status = "Up to date";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(
|
|
121
|
+
` ${pkg.padEnd(30)}${(current ?? '').padEnd(14)}${remote.padEnd(14)}${status}`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log();
|
|
126
|
+
|
|
127
|
+
if (hasUpdates) {
|
|
128
|
+
logger.warn('Updates available! Run "actuate upgrade" to update.');
|
|
129
|
+
} else {
|
|
130
|
+
logger.success("All packages are up to date!");
|
|
131
|
+
}
|
|
132
|
+
} catch (err) {
|
|
133
|
+
spinner.fail("Update check failed.");
|
|
134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
135
|
+
logger.error(message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function registerUpdateCheckCommand(program: Command): void {
|
|
141
|
+
program
|
|
142
|
+
.command("update-check")
|
|
143
|
+
.description(
|
|
144
|
+
"Check for available Actuate CMS package updates",
|
|
145
|
+
)
|
|
146
|
+
.action(runUpdateCheck);
|
|
147
|
+
}
|
package/src/commands/upgrade.ts
CHANGED
|
@@ -1,173 +1,173 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
2
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
4
|
-
import { resolve } from "node:path";
|
|
5
|
-
import { createInterface } from "node:readline/promises";
|
|
6
|
-
import ora from "ora";
|
|
7
|
-
import { logger } from "../utils/logger.js";
|
|
8
|
-
|
|
9
|
-
const UPDATE_SERVER_URL = "https://updates.actuatecms.com/api/versions";
|
|
10
|
-
|
|
11
|
-
function compareVersions(a: string, b: string): number {
|
|
12
|
-
const pa = a.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
13
|
-
const pb = b.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
14
|
-
for (let i = 0; i < 3; i++) {
|
|
15
|
-
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
16
|
-
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
17
|
-
}
|
|
18
|
-
return 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async function confirm(question: string): Promise<boolean> {
|
|
22
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
-
try {
|
|
24
|
-
const answer = await rl.question(`${question} (y/N) `);
|
|
25
|
-
return answer.trim().toLowerCase() === "y";
|
|
26
|
-
} finally {
|
|
27
|
-
rl.close();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function fetchLatestVersions(
|
|
32
|
-
packages: string[],
|
|
33
|
-
): Promise<Record<string, string>> {
|
|
34
|
-
const versions: Record<string, string> = {};
|
|
35
|
-
|
|
36
|
-
try {
|
|
37
|
-
const res = await fetch(UPDATE_SERVER_URL);
|
|
38
|
-
if (res.ok) {
|
|
39
|
-
const data = await res.json();
|
|
40
|
-
for (const pkg of packages) {
|
|
41
|
-
if (data[pkg]) versions[pkg] = data[pkg];
|
|
42
|
-
}
|
|
43
|
-
if (Object.keys(versions).length === packages.length) return versions;
|
|
44
|
-
}
|
|
45
|
-
} catch {
|
|
46
|
-
// Fall through to npm registry
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
for (const pkg of packages) {
|
|
50
|
-
if (versions[pkg]) continue;
|
|
51
|
-
try {
|
|
52
|
-
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
|
|
53
|
-
if (res.ok) {
|
|
54
|
-
const data = await res.json();
|
|
55
|
-
versions[pkg] = data.version;
|
|
56
|
-
}
|
|
57
|
-
} catch {
|
|
58
|
-
// Skip
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return versions;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
interface UpgradeOptions {
|
|
66
|
-
latest?: boolean;
|
|
67
|
-
version?: string;
|
|
68
|
-
dryRun?: boolean;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async function runUpgrade(options: UpgradeOptions): Promise<void> {
|
|
72
|
-
const pkgPath = resolve(process.cwd(), "package.json");
|
|
73
|
-
if (!existsSync(pkgPath)) {
|
|
74
|
-
logger.error("No package.json found in the current directory.");
|
|
75
|
-
process.exit(1);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const raw = await readFile(pkgPath, "utf-8");
|
|
79
|
-
const pkg = JSON.parse(raw);
|
|
80
|
-
|
|
81
|
-
const actuatePackages: { name: string; current: string; section: string }[] = [];
|
|
82
|
-
|
|
83
|
-
for (const section of ["dependencies", "devDependencies"] as const) {
|
|
84
|
-
const deps = pkg[section];
|
|
85
|
-
if (!deps) continue;
|
|
86
|
-
for (const [name, version] of Object.entries(deps)) {
|
|
87
|
-
if (name.startsWith("@actuate-media/") && typeof version === "string") {
|
|
88
|
-
if (version.startsWith("workspace:")) continue;
|
|
89
|
-
actuatePackages.push({
|
|
90
|
-
name,
|
|
91
|
-
current: version.replace(/^[\^~]/, ""),
|
|
92
|
-
section,
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (actuatePackages.length === 0) {
|
|
99
|
-
logger.warn("No @actuate-media/* packages found in package.json (excluding workspace: references).");
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
const spinner = ora("Resolving target versions…").start();
|
|
104
|
-
|
|
105
|
-
let targetVersions: Record<string, string>;
|
|
106
|
-
|
|
107
|
-
if (options.version) {
|
|
108
|
-
targetVersions = {};
|
|
109
|
-
for (const p of actuatePackages) {
|
|
110
|
-
targetVersions[p.name] = options.version;
|
|
111
|
-
}
|
|
112
|
-
spinner.succeed(`Target version: ${options.version}`);
|
|
113
|
-
} else {
|
|
114
|
-
targetVersions = await fetchLatestVersions(
|
|
115
|
-
actuatePackages.map((p) => p.name),
|
|
116
|
-
);
|
|
117
|
-
spinner.succeed("Resolved latest versions.");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const upgrades: { name: string; from: string; to: string; section: string }[] = [];
|
|
121
|
-
|
|
122
|
-
for (const p of actuatePackages) {
|
|
123
|
-
const target = targetVersions[p.name];
|
|
124
|
-
if (!target) continue;
|
|
125
|
-
if (compareVersions(p.current, target) < 0) {
|
|
126
|
-
upgrades.push({ name: p.name, from: p.current, to: target, section: p.section });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (upgrades.length === 0) {
|
|
131
|
-
logger.success("All @actuate-media/* packages are already up to date.");
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
console.log("\n Proposed upgrades:\n");
|
|
136
|
-
console.log(" " + "Package".padEnd(30) + "From".padEnd(14) + "To");
|
|
137
|
-
console.log(" " + "-".repeat(54));
|
|
138
|
-
for (const u of upgrades) {
|
|
139
|
-
console.log(` ${u.name.padEnd(30)}${u.from.padEnd(14)}${u.to}`);
|
|
140
|
-
}
|
|
141
|
-
console.log();
|
|
142
|
-
|
|
143
|
-
if (options.dryRun) {
|
|
144
|
-
logger.info("Dry run — no changes were made.");
|
|
145
|
-
return;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const proceed = await confirm("Apply these upgrades to package.json?");
|
|
149
|
-
if (!proceed) {
|
|
150
|
-
logger.warn("Upgrade cancelled.");
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
for (const u of upgrades) {
|
|
155
|
-
const prefix = pkg[u.section][u.name]?.match(/^[\^~]/)?.[0] ?? "^";
|
|
156
|
-
pkg[u.section][u.name] = `${prefix}${u.to}`;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
160
|
-
|
|
161
|
-
logger.success(`Updated ${upgrades.length} package(s) in package.json.`);
|
|
162
|
-
logger.info('Run "pnpm install" to apply the upgrade.');
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
export function registerUpgradeCommand(program: Command): void {
|
|
166
|
-
program
|
|
167
|
-
.command("upgrade")
|
|
168
|
-
.description("Upgrade @actuate-media/* packages to the latest or a specific version")
|
|
169
|
-
.option("--latest", "Upgrade to the latest version (default)", true)
|
|
170
|
-
.option("--version <ver>", "Upgrade to a specific version")
|
|
171
|
-
.option("--dry-run", "Show proposed changes without modifying files")
|
|
172
|
-
.action(runUpgrade);
|
|
173
|
-
}
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline/promises";
|
|
6
|
+
import ora from "ora";
|
|
7
|
+
import { logger } from "../utils/logger.js";
|
|
8
|
+
|
|
9
|
+
const UPDATE_SERVER_URL = "https://updates.actuatecms.com/api/versions";
|
|
10
|
+
|
|
11
|
+
function compareVersions(a: string, b: string): number {
|
|
12
|
+
const pa = a.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
13
|
+
const pb = b.replace(/^[^0-9]*/, "").split(".").map(Number);
|
|
14
|
+
for (let i = 0; i < 3; i++) {
|
|
15
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
16
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
17
|
+
}
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function confirm(question: string): Promise<boolean> {
|
|
22
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
23
|
+
try {
|
|
24
|
+
const answer = await rl.question(`${question} (y/N) `);
|
|
25
|
+
return answer.trim().toLowerCase() === "y";
|
|
26
|
+
} finally {
|
|
27
|
+
rl.close();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function fetchLatestVersions(
|
|
32
|
+
packages: string[],
|
|
33
|
+
): Promise<Record<string, string>> {
|
|
34
|
+
const versions: Record<string, string> = {};
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(UPDATE_SERVER_URL);
|
|
38
|
+
if (res.ok) {
|
|
39
|
+
const data = await res.json();
|
|
40
|
+
for (const pkg of packages) {
|
|
41
|
+
if (data[pkg]) versions[pkg] = data[pkg];
|
|
42
|
+
}
|
|
43
|
+
if (Object.keys(versions).length === packages.length) return versions;
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// Fall through to npm registry
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
for (const pkg of packages) {
|
|
50
|
+
if (versions[pkg]) continue;
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetch(`https://registry.npmjs.org/${pkg}/latest`);
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
const data = await res.json();
|
|
55
|
+
versions[pkg] = data.version;
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
// Skip
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return versions;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface UpgradeOptions {
|
|
66
|
+
latest?: boolean;
|
|
67
|
+
version?: string;
|
|
68
|
+
dryRun?: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async function runUpgrade(options: UpgradeOptions): Promise<void> {
|
|
72
|
+
const pkgPath = resolve(process.cwd(), "package.json");
|
|
73
|
+
if (!existsSync(pkgPath)) {
|
|
74
|
+
logger.error("No package.json found in the current directory.");
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const raw = await readFile(pkgPath, "utf-8");
|
|
79
|
+
const pkg = JSON.parse(raw);
|
|
80
|
+
|
|
81
|
+
const actuatePackages: { name: string; current: string; section: string }[] = [];
|
|
82
|
+
|
|
83
|
+
for (const section of ["dependencies", "devDependencies"] as const) {
|
|
84
|
+
const deps = pkg[section];
|
|
85
|
+
if (!deps) continue;
|
|
86
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
87
|
+
if (name.startsWith("@actuate-media/") && typeof version === "string") {
|
|
88
|
+
if (version.startsWith("workspace:")) continue;
|
|
89
|
+
actuatePackages.push({
|
|
90
|
+
name,
|
|
91
|
+
current: version.replace(/^[\^~]/, ""),
|
|
92
|
+
section,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (actuatePackages.length === 0) {
|
|
99
|
+
logger.warn("No @actuate-media/* packages found in package.json (excluding workspace: references).");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const spinner = ora("Resolving target versions…").start();
|
|
104
|
+
|
|
105
|
+
let targetVersions: Record<string, string>;
|
|
106
|
+
|
|
107
|
+
if (options.version) {
|
|
108
|
+
targetVersions = {};
|
|
109
|
+
for (const p of actuatePackages) {
|
|
110
|
+
targetVersions[p.name] = options.version;
|
|
111
|
+
}
|
|
112
|
+
spinner.succeed(`Target version: ${options.version}`);
|
|
113
|
+
} else {
|
|
114
|
+
targetVersions = await fetchLatestVersions(
|
|
115
|
+
actuatePackages.map((p) => p.name),
|
|
116
|
+
);
|
|
117
|
+
spinner.succeed("Resolved latest versions.");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const upgrades: { name: string; from: string; to: string; section: string }[] = [];
|
|
121
|
+
|
|
122
|
+
for (const p of actuatePackages) {
|
|
123
|
+
const target = targetVersions[p.name];
|
|
124
|
+
if (!target) continue;
|
|
125
|
+
if (compareVersions(p.current, target) < 0) {
|
|
126
|
+
upgrades.push({ name: p.name, from: p.current, to: target, section: p.section });
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (upgrades.length === 0) {
|
|
131
|
+
logger.success("All @actuate-media/* packages are already up to date.");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log("\n Proposed upgrades:\n");
|
|
136
|
+
console.log(" " + "Package".padEnd(30) + "From".padEnd(14) + "To");
|
|
137
|
+
console.log(" " + "-".repeat(54));
|
|
138
|
+
for (const u of upgrades) {
|
|
139
|
+
console.log(` ${u.name.padEnd(30)}${u.from.padEnd(14)}${u.to}`);
|
|
140
|
+
}
|
|
141
|
+
console.log();
|
|
142
|
+
|
|
143
|
+
if (options.dryRun) {
|
|
144
|
+
logger.info("Dry run — no changes were made.");
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const proceed = await confirm("Apply these upgrades to package.json?");
|
|
149
|
+
if (!proceed) {
|
|
150
|
+
logger.warn("Upgrade cancelled.");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
for (const u of upgrades) {
|
|
155
|
+
const prefix = pkg[u.section][u.name]?.match(/^[\^~]/)?.[0] ?? "^";
|
|
156
|
+
pkg[u.section][u.name] = `${prefix}${u.to}`;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf-8");
|
|
160
|
+
|
|
161
|
+
logger.success(`Updated ${upgrades.length} package(s) in package.json.`);
|
|
162
|
+
logger.info('Run "pnpm install" to apply the upgrade.');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function registerUpgradeCommand(program: Command): void {
|
|
166
|
+
program
|
|
167
|
+
.command("upgrade")
|
|
168
|
+
.description("Upgrade @actuate-media/* packages to the latest or a specific version")
|
|
169
|
+
.option("--latest", "Upgrade to the latest version (default)", true)
|
|
170
|
+
.option("--version <ver>", "Upgrade to a specific version")
|
|
171
|
+
.option("--dry-run", "Show proposed changes without modifying files")
|
|
172
|
+
.action(runUpgrade);
|
|
173
|
+
}
|