@tutti-os/app-release-tools 0.0.10 → 0.0.11
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 +0 -4
- package/bin/build-tutti-app-release.mjs +35 -0
- package/package.json +1 -3
- package/bin/bump-tutti-app-version.mjs +0 -119
package/README.md
CHANGED
|
@@ -8,16 +8,12 @@ Command-line tools for publishing Tutti workspace apps into App Center release m
|
|
|
8
8
|
build-tutti-app-release --app-id vibe-design --package-dir dist/tutti-app/vibe-design --base-url https://cdn.example.test/tutti-app-releases
|
|
9
9
|
build-tutti-app-catalog --release-file ./apps/vibe-design/latest.json --output ./catalog.json
|
|
10
10
|
build-tutti-app-catalog --existing-catalog ./catalog.json --release-file ./apps/vibe-design/latest.json --output ./catalog.json
|
|
11
|
-
bump-tutti-app-version --app-id vibe-design --manifest ./tutti.app.json --bump patch
|
|
12
11
|
verify-tutti-app-release-artifacts --release-file ./apps/vibe-design/latest.json
|
|
13
12
|
verify-tutti-app-release-artifacts --catalog-file ./catalog.json --release-file ./apps/vibe-design/latest.json
|
|
14
13
|
```
|
|
15
14
|
|
|
16
15
|
The release command validates a complete Tutti app package, creates a zip, writes immutable `release.json`, and writes mutable `latest.json`.
|
|
17
16
|
|
|
18
|
-
The version bump command updates an app manifest from one stable semver version
|
|
19
|
-
to the next major, minor, or patch version.
|
|
20
|
-
|
|
21
17
|
The catalog command merges one or more release files into `tutti.app.catalog.v1`.
|
|
22
18
|
Pass `--existing-catalog` to preserve existing catalog apps and update only the
|
|
23
19
|
apps represented by the release files. With `--existing-catalog`, release files
|
|
@@ -150,6 +150,7 @@ export function validateManifest(manifest, sourceLabel = "manifest") {
|
|
|
150
150
|
throw new Error(`${sourceLabel} runtime.healthcheckPath must start with /`);
|
|
151
151
|
}
|
|
152
152
|
validateManifestCLI(manifest.cli, sourceLabel);
|
|
153
|
+
validateManifestReferences(manifest.references, sourceLabel);
|
|
153
154
|
validateLocalizationInfo(manifest.localizationInfo, sourceLabel);
|
|
154
155
|
}
|
|
155
156
|
|
|
@@ -171,6 +172,24 @@ function validateManifestCLI(cli, sourceLabel) {
|
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
174
|
|
|
175
|
+
function validateManifestReferences(references, sourceLabel) {
|
|
176
|
+
if (references === undefined) {
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (!references || typeof references !== "object") {
|
|
180
|
+
throw new Error(`${sourceLabel} references must be an object`);
|
|
181
|
+
}
|
|
182
|
+
const searchEndpoint = requireNonEmpty(
|
|
183
|
+
references.searchEndpoint,
|
|
184
|
+
`${sourceLabel}.references.searchEndpoint`
|
|
185
|
+
);
|
|
186
|
+
if (!isRelativeURLPath(searchEndpoint)) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
`${sourceLabel}.references.searchEndpoint must be a relative URL path without query or hash`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
174
193
|
export function validateCLIManifest(manifest, sourceLabel = "cli manifest") {
|
|
175
194
|
if (!manifest || typeof manifest !== "object") {
|
|
176
195
|
throw new Error(`${sourceLabel} must be an object`);
|
|
@@ -495,6 +514,22 @@ function isRelativePackagePath(value) {
|
|
|
495
514
|
return !text.split(/[\\/]+/).includes("..");
|
|
496
515
|
}
|
|
497
516
|
|
|
517
|
+
function isRelativeURLPath(value) {
|
|
518
|
+
const text = String(value ?? "").trim();
|
|
519
|
+
if (
|
|
520
|
+
text === "" ||
|
|
521
|
+
!text.startsWith("/") ||
|
|
522
|
+
text.startsWith("//") ||
|
|
523
|
+
text.includes("\0") ||
|
|
524
|
+
text.includes("?") ||
|
|
525
|
+
text.includes("#") ||
|
|
526
|
+
text.includes("%")
|
|
527
|
+
) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
|
|
498
533
|
function normalizeBaseUrl(value) {
|
|
499
534
|
return String(value).trim().replace(/\/+$/, "");
|
|
500
535
|
}
|
package/package.json
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tutti-os/app-release-tools",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.11",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"build-tutti-app-release": "./bin/build-tutti-app-release.mjs",
|
|
8
8
|
"build-tutti-app-catalog": "./bin/build-tutti-app-catalog.mjs",
|
|
9
|
-
"bump-tutti-app-version": "./bin/bump-tutti-app-version.mjs",
|
|
10
9
|
"verify-tutti-app-release-artifacts": "./bin/verify-tutti-app-release-artifacts.mjs"
|
|
11
10
|
},
|
|
12
11
|
"exports": {
|
|
13
12
|
"./build-tutti-app-release": "./bin/build-tutti-app-release.mjs",
|
|
14
13
|
"./build-tutti-app-catalog": "./bin/build-tutti-app-catalog.mjs",
|
|
15
|
-
"./bump-tutti-app-version": "./bin/bump-tutti-app-version.mjs",
|
|
16
14
|
"./verify-tutti-app-release-artifacts": "./bin/verify-tutti-app-release-artifacts.mjs"
|
|
17
15
|
},
|
|
18
16
|
"files": [
|
|
@@ -1,119 +0,0 @@
|
|
|
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-tutti-app-release.mjs";
|
|
7
|
-
|
|
8
|
-
const allowedBumps = new Set(["major", "minor", "patch"]);
|
|
9
|
-
|
|
10
|
-
export async function bumpTuttiAppVersion(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 bumpTuttiAppVersion(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
|
-
}
|