@tutti-os/app-release-tools 0.0.1 → 0.0.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/README.md CHANGED
@@ -8,12 +8,16 @@ Command-line tools for publishing Nextop workspace apps into App Center release
8
8
  build-nextop-app-release --app-id vibe-design --package-dir dist/nextop-app/vibe-design --base-url https://cdn.example.test/nextop-app-releases
9
9
  build-nextop-app-catalog --release-file ./apps/vibe-design/latest.json --output ./catalog.json
10
10
  build-nextop-app-catalog --existing-catalog ./catalog.json --release-file ./apps/vibe-design/latest.json --output ./catalog.json
11
+ bump-nextop-app-version --app-id vibe-design --manifest ./nextop.app.json --bump patch
11
12
  verify-nextop-app-release-artifacts --release-file ./apps/vibe-design/latest.json
12
13
  verify-nextop-app-release-artifacts --catalog-file ./catalog.json --release-file ./apps/vibe-design/latest.json
13
14
  ```
14
15
 
15
16
  The release command validates a complete Nextop app package, creates a zip, writes immutable `release.json`, and writes mutable `latest.json`.
16
17
 
18
+ The version bump command updates an app manifest from one stable semver version
19
+ to the next major, minor, or patch version.
20
+
17
21
  The catalog command merges one or more release files into `nextop.app.catalog.v1`.
18
22
  Pass `--existing-catalog` to preserve existing catalog apps and update only the
19
23
  apps represented by the release files. With `--existing-catalog`, release files
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+ import { readFile, writeFile } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { pathToFileURL } from "node:url";
5
+
6
+ import { validateManifest } from "./build-nextop-app-release.mjs";
7
+
8
+ const allowedBumps = new Set(["major", "minor", "patch"]);
9
+
10
+ export async function bumpNextopAppVersion(options) {
11
+ const manifestPath = path.resolve(
12
+ requireNonEmpty(options.manifestPath, "manifestPath")
13
+ );
14
+ const bump = String(options.bump ?? "patch").trim();
15
+ if (!allowedBumps.has(bump)) {
16
+ throw new Error("bump must be one of major, minor, or patch");
17
+ }
18
+
19
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8"));
20
+ validateManifest(manifest, manifestPath);
21
+ if (options.appId && manifest.appId !== options.appId) {
22
+ throw new Error(
23
+ `appId mismatch: input ${options.appId} does not match manifest ${manifest.appId}`
24
+ );
25
+ }
26
+
27
+ const previousVersion = manifest.version;
28
+ const version = bumpStableVersion(previousVersion, bump);
29
+ manifest.version = version;
30
+ validateManifest(manifest, manifestPath);
31
+ await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);
32
+
33
+ return {
34
+ appId: manifest.appId,
35
+ manifestPath,
36
+ previousVersion,
37
+ version
38
+ };
39
+ }
40
+
41
+ function bumpStableVersion(version, bump) {
42
+ const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(String(version ?? "").trim());
43
+ if (!match) {
44
+ throw new Error(
45
+ `manifest version must be stable semver x.y.z for automatic bump, got ${JSON.stringify(version)}`
46
+ );
47
+ }
48
+
49
+ let major = Number(match[1]);
50
+ let minor = Number(match[2]);
51
+ let patch = Number(match[3]);
52
+ if (bump === "major") {
53
+ major += 1;
54
+ minor = 0;
55
+ patch = 0;
56
+ } else if (bump === "minor") {
57
+ minor += 1;
58
+ patch = 0;
59
+ } else {
60
+ patch += 1;
61
+ }
62
+
63
+ return `${major}.${minor}.${patch}`;
64
+ }
65
+
66
+ function requireNonEmpty(value, label) {
67
+ const text = String(value ?? "").trim();
68
+ if (text === "") {
69
+ throw new Error(`${label} is required`);
70
+ }
71
+ return text;
72
+ }
73
+
74
+ function parseArgs(argv) {
75
+ const result = {};
76
+ for (let index = 0; index < argv.length; index += 1) {
77
+ const arg = argv[index];
78
+ if (arg === "--app-id") {
79
+ result.appId = readArgValue(argv, index, arg);
80
+ index += 1;
81
+ continue;
82
+ }
83
+ if (arg === "--bump") {
84
+ result.bump = readArgValue(argv, index, arg);
85
+ index += 1;
86
+ continue;
87
+ }
88
+ if (arg === "--manifest") {
89
+ result.manifestPath = readArgValue(argv, index, arg);
90
+ index += 1;
91
+ continue;
92
+ }
93
+ throw new Error(`unexpected argument: ${arg}`);
94
+ }
95
+ return result;
96
+ }
97
+
98
+ function readArgValue(argv, index, arg) {
99
+ const value = argv[index + 1];
100
+ if (!value || value.startsWith("--")) {
101
+ throw new Error(`missing value for ${arg}`);
102
+ }
103
+ return value;
104
+ }
105
+
106
+ export async function main() {
107
+ const result = await bumpNextopAppVersion(parseArgs(process.argv.slice(2)));
108
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
109
+ }
110
+
111
+ if (
112
+ process.argv[1] &&
113
+ import.meta.url === pathToFileURL(process.argv[1]).href
114
+ ) {
115
+ main().catch((error) => {
116
+ console.error(error.message);
117
+ process.exitCode = 1;
118
+ });
119
+ }
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "@tutti-os/app-release-tools",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "build-nextop-app-release": "./bin/build-nextop-app-release.mjs",
8
8
  "build-nextop-app-catalog": "./bin/build-nextop-app-catalog.mjs",
9
+ "bump-nextop-app-version": "./bin/bump-nextop-app-version.mjs",
9
10
  "verify-nextop-app-release-artifacts": "./bin/verify-nextop-app-release-artifacts.mjs"
10
11
  },
11
12
  "exports": {
12
13
  "./build-nextop-app-release": "./bin/build-nextop-app-release.mjs",
13
14
  "./build-nextop-app-catalog": "./bin/build-nextop-app-catalog.mjs",
15
+ "./bump-nextop-app-version": "./bin/bump-nextop-app-version.mjs",
14
16
  "./verify-nextop-app-release-artifacts": "./bin/verify-nextop-app-release-artifacts.mjs"
15
17
  },
16
18
  "files": [