@moku-labs/ci 1.2.1 → 1.2.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/dist/release.mjs +76 -14
- package/package.json +4 -4
package/dist/release.mjs
CHANGED
|
@@ -774,16 +774,34 @@ function parseManifest(text) {
|
|
|
774
774
|
async function readManifest(files) {
|
|
775
775
|
return parseManifest(await files.read(MANIFEST_PATH));
|
|
776
776
|
}
|
|
777
|
+
/** Any character outside printable ASCII and the JSON whitespace. */
|
|
778
|
+
const NON_ASCII = /[^\t\n\r -~]/;
|
|
779
|
+
/**
|
|
780
|
+
* Whether a text holds only ASCII characters, i.e. every other character is `\uXXXX`-escaped.
|
|
781
|
+
*
|
|
782
|
+
* @param text - The file contents to test.
|
|
783
|
+
* @returns `true` when no raw non-ASCII character is present.
|
|
784
|
+
* @example
|
|
785
|
+
* isAsciiOnly('{"description":"a \\u2014 b"}'); // true
|
|
786
|
+
*/
|
|
787
|
+
function isAsciiOnly(text) {
|
|
788
|
+
return !NON_ASCII.test(text);
|
|
789
|
+
}
|
|
777
790
|
/**
|
|
778
791
|
* Render a manifest the way npm itself writes one: two-space JSON with a trailing newline.
|
|
792
|
+
* A source file that was ASCII-only stays ASCII-only, so an escaped dash the package chose
|
|
793
|
+
* does not show up as a changed line in the diff.
|
|
779
794
|
*
|
|
780
795
|
* @param manifest - The manifest to serialize.
|
|
796
|
+
* @param source - The file contents the manifest was read from, when there were any.
|
|
781
797
|
* @returns The file contents to write.
|
|
782
798
|
* @example
|
|
783
|
-
* await files.write("package.json", formatManifest(manifest));
|
|
799
|
+
* await files.write("package.json", formatManifest(manifest, source));
|
|
784
800
|
*/
|
|
785
|
-
function formatManifest(manifest) {
|
|
786
|
-
|
|
801
|
+
function formatManifest(manifest, source) {
|
|
802
|
+
const text = `${JSON.stringify(manifest, void 0, 2)}\n`;
|
|
803
|
+
if (source === void 0 || !isAsciiOnly(source)) return text;
|
|
804
|
+
return text.replaceAll(new RegExp(NON_ASCII, "g"), (character) => String.raw`\u${character.codePointAt(0)?.toString(16).padStart(4, "0")}`);
|
|
787
805
|
}
|
|
788
806
|
/**
|
|
789
807
|
* The repository URL a manifest declares, in either the object or shorthand string form.
|
|
@@ -2097,9 +2115,55 @@ async function ensurePrerequisites(setup) {
|
|
|
2097
2115
|
return true;
|
|
2098
2116
|
}
|
|
2099
2117
|
/**
|
|
2118
|
+
* Whether git already holds this exact file: tracked, and with no uncommitted change.
|
|
2119
|
+
*
|
|
2120
|
+
* @param setup - The wizard state.
|
|
2121
|
+
* @param path - Repo-relative path of the file.
|
|
2122
|
+
* @returns `true` when `git checkout -- <path>` would bring the current content back.
|
|
2123
|
+
* @example
|
|
2124
|
+
* await isCommittedUnchanged(setup, ".github/workflows/ci.yml");
|
|
2125
|
+
*/
|
|
2126
|
+
async function isCommittedUnchanged(setup, path) {
|
|
2127
|
+
if ((await setup.ctx.exec.capture("git", [
|
|
2128
|
+
"ls-files",
|
|
2129
|
+
"--error-unmatch",
|
|
2130
|
+
path
|
|
2131
|
+
])).code !== 0) return false;
|
|
2132
|
+
const status = await setup.ctx.exec.capture("git", [
|
|
2133
|
+
"status",
|
|
2134
|
+
"--porcelain",
|
|
2135
|
+
"--",
|
|
2136
|
+
path
|
|
2137
|
+
]);
|
|
2138
|
+
return status.code === 0 && status.stdout.trim() === "";
|
|
2139
|
+
}
|
|
2140
|
+
/**
|
|
2141
|
+
* Ask before replacing an existing workflow, and keep the original: in git when it is
|
|
2142
|
+
* committed and unmodified, in a `.bak` copy otherwise.
|
|
2143
|
+
*
|
|
2144
|
+
* @param setup - The wizard state.
|
|
2145
|
+
* @param template - The central workflow about to be written.
|
|
2146
|
+
* @param existing - The current contents of the file.
|
|
2147
|
+
* @returns `true` when the caller may write the template now.
|
|
2148
|
+
* @example
|
|
2149
|
+
* if (!(await clearExistingWorkflow(setup, template, existing))) continue;
|
|
2150
|
+
*/
|
|
2151
|
+
async function clearExistingWorkflow(setup, template, existing) {
|
|
2152
|
+
const kind = isThinWorkflow(existing, template) ? "differs" : "is a legacy workflow";
|
|
2153
|
+
const committed = await isCommittedUnchanged(setup, template.path);
|
|
2154
|
+
const safety = committed ? "git keeps the original" : "a .bak copy is kept";
|
|
2155
|
+
if (!await setup.prompts.confirm(`${template.path} ${kind}. Replace it (${safety})?`)) {
|
|
2156
|
+
setup.ui.check(false, `${template.path} left unchanged`);
|
|
2157
|
+
return false;
|
|
2158
|
+
}
|
|
2159
|
+
if (deferred(setup, `${committed ? "replace" : "back up and replace"} ${template.path}`)) return false;
|
|
2160
|
+
if (!committed) await setup.ctx.files.backup(template.path);
|
|
2161
|
+
return true;
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2100
2164
|
* Write the two thin workflows. A file that already calls the pinned central workflow is
|
|
2101
2165
|
* left alone; a differing file is only replaced after an explicit confirm, and a `.bak`
|
|
2102
|
-
* copy is kept.
|
|
2166
|
+
* copy is kept unless git already holds the original.
|
|
2103
2167
|
*
|
|
2104
2168
|
* @param setup - The wizard state.
|
|
2105
2169
|
* @returns Nothing.
|
|
@@ -2114,15 +2178,7 @@ async function writeWorkflows(setup) {
|
|
|
2114
2178
|
setup.ui.check(true, `${template.path} up to date`);
|
|
2115
2179
|
continue;
|
|
2116
2180
|
}
|
|
2117
|
-
if (existing
|
|
2118
|
-
const kind = isThinWorkflow(existing, template) ? "differs" : "is a legacy workflow";
|
|
2119
|
-
if (!await setup.prompts.confirm(`${template.path} ${kind}. Replace it (a .bak copy is kept)?`)) {
|
|
2120
|
-
setup.ui.check(false, `${template.path} left unchanged`);
|
|
2121
|
-
continue;
|
|
2122
|
-
}
|
|
2123
|
-
if (deferred(setup, `back up and replace ${template.path}`)) continue;
|
|
2124
|
-
await setup.ctx.files.backup(template.path);
|
|
2125
|
-
} else if (deferred(setup, `write ${template.path}`)) continue;
|
|
2181
|
+
if (!(existing === void 0 ? !deferred(setup, `write ${template.path}`) : await clearExistingWorkflow(setup, template, existing))) continue;
|
|
2126
2182
|
await setup.ctx.files.write(template.path, template.content);
|
|
2127
2183
|
setup.ui.check(true, `${template.path} written`);
|
|
2128
2184
|
}
|
|
@@ -2155,7 +2211,8 @@ async function normalizeContract(setup, manifest) {
|
|
|
2155
2211
|
return;
|
|
2156
2212
|
}
|
|
2157
2213
|
if (deferred(setup, `write package.json`)) return;
|
|
2158
|
-
await setup.ctx.files.
|
|
2214
|
+
const source = await setup.ctx.files.read(MANIFEST_PATH);
|
|
2215
|
+
await setup.ctx.files.write(MANIFEST_PATH, formatManifest(next, source));
|
|
2159
2216
|
setup.ui.check(true, `${MANIFEST_PATH} normalized`);
|
|
2160
2217
|
}
|
|
2161
2218
|
/**
|
|
@@ -2209,6 +2266,11 @@ async function firstPublish(setup, name, version) {
|
|
|
2209
2266
|
*/
|
|
2210
2267
|
async function pushVersionTag(setup, version) {
|
|
2211
2268
|
setup.ui.heading("Tag");
|
|
2269
|
+
const latest = latestVersionTag((await setup.ctx.exec.capture("git", [...LATEST_TAG_ARGS])).stdout);
|
|
2270
|
+
if (latest !== void 0) {
|
|
2271
|
+
setup.ui.check(true, `release tags exist, latest is ${latest}`);
|
|
2272
|
+
return;
|
|
2273
|
+
}
|
|
2212
2274
|
const tag = `v${version}`;
|
|
2213
2275
|
if ((await setup.ctx.exec.capture("git", [
|
|
2214
2276
|
"tag",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moku-labs/ci",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.2",
|
|
4
4
|
"description": "Central CI and release for the moku family: reusable workflows, caller examples and the moku-release CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": [
|
|
@@ -56,8 +56,8 @@
|
|
|
56
56
|
"lint:fix": "biome check --write . && eslint --fix .",
|
|
57
57
|
"format": "biome format --write .",
|
|
58
58
|
"test": "vitest run",
|
|
59
|
-
"release:setup": "
|
|
60
|
-
"release:doctor": "
|
|
61
|
-
"release": "
|
|
59
|
+
"release:setup": "bun run build && node dist/release.mjs setup",
|
|
60
|
+
"release:doctor": "bun run build && node dist/release.mjs doctor",
|
|
61
|
+
"release": "bun run build && node dist/release.mjs"
|
|
62
62
|
}
|
|
63
63
|
}
|