@heroiclands/package-build 23.1.0 → 23.1.1
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/CHANGELOG.md +8 -0
- package/docs/guides/release-and-deploy.md +22 -6
- package/engine/yaml-lint.mjs +7 -4
- package/package.json +1 -1
- package/release.mjs +62 -4
- package/types/engine/yaml-lint.d.mts +5 -4
- package/types/release.d.mts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @heroiclands/package-build
|
|
2
2
|
|
|
3
|
+
## 23.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
`package-build yaml` accepts an empty trigger such as `workflow_dispatch:` in a Gitea workflow under `.gitea/workflows/`, as it already does under `.github/workflows/`.
|
|
8
|
+
|
|
9
|
+
Rebuilding a release of the same commit produces an identical `module.zip` or `system.zip`, so a rebuilt archive can be compared with the one already published. Setting `SOURCE_DATE_EPOCH` stamps the archive's files with that time.
|
|
10
|
+
|
|
3
11
|
## 23.1.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -105,12 +105,28 @@ change is minor; an optional command, flag, or key is patch. A major bump
|
|
|
105
105
|
requires the maintainer's direct decision. `package-build changelog check`
|
|
106
106
|
checks the quality of any pending changeset.
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
108
|
+
The repository's primary forge is Gitea, and GitHub is its push mirror. The
|
|
109
|
+
release runs in two halves, one on each:
|
|
110
|
+
|
|
111
|
+
1. **Version, on Gitea.** Merging a changeset to `main` runs
|
|
112
|
+
`.gitea/workflows/release.yml`, which opens or updates the **Version
|
|
113
|
+
Packages** pull request. Its generated changelog section is reviewed as the
|
|
114
|
+
release note. Merging that pull request runs the same workflow again, which
|
|
115
|
+
pushes the tag `v<version>` at the merged version commit. Releasing from
|
|
116
|
+
Gitea is switched on by the repository variable `HL_RELEASE_ENABLED`.
|
|
117
|
+
2. **Publish, on GitHub.** The push mirror carries the tag to GitHub, where it
|
|
118
|
+
starts the **Publish to npm** workflow, `.github/workflows/release.yml`. It
|
|
119
|
+
refuses a tag that names a commit outside `main` or that is not
|
|
120
|
+
`v<version>` of the tagged `package.json`. When npm does not have the
|
|
121
|
+
version yet, it runs the tests and publishes through npm Trusted Publishing
|
|
122
|
+
with provenance. It then creates the GitHub Release from the version's
|
|
123
|
+
changelog section, unless one exists.
|
|
124
|
+
|
|
125
|
+
Nothing on Gitea publishes to npm, and nothing on GitHub runs for a branch
|
|
126
|
+
push. Every step of the GitHub half is safe to repeat: re-running it, or
|
|
127
|
+
dispatching it with the tag as its ref, finishes a release whose publish or
|
|
128
|
+
Release step failed and changes nothing for one that completed. The
|
|
129
|
+
[npm package page](https://www.npmjs.com/package/@heroiclands/package-build)
|
|
114
130
|
shows the published version and README.
|
|
115
131
|
|
|
116
132
|
Two scripts in this repository's own `package.json` run the same steps at a
|
package/engine/yaml-lint.mjs
CHANGED
|
@@ -104,10 +104,11 @@ export const frontmatterProcessor = {
|
|
|
104
104
|
* - `no-empty-key`, `no-empty-document` — a fence or a file that parses to
|
|
105
105
|
* nothing at all.
|
|
106
106
|
*
|
|
107
|
-
* **
|
|
108
|
-
*
|
|
109
|
-
*
|
|
110
|
-
*
|
|
107
|
+
* **Workflows are exempt from `no-empty-mapping-value`**, under
|
|
108
|
+
* `.github/workflows/` and Gitea's `.gitea/workflows/` alike, because an empty
|
|
109
|
+
* value is the language there: `on:` `push:` and `workflow_dispatch:` carry
|
|
110
|
+
* their meaning by being present, and writing `push: null` to satisfy a linter
|
|
111
|
+
* would be worse YAML, not better.
|
|
111
112
|
*
|
|
112
113
|
* @param {object} plugin - The `eslint-plugin-yml` module.
|
|
113
114
|
* @returns {object[]} A complete flat config, for `overrideConfig`.
|
|
@@ -128,6 +129,8 @@ export function yamlLintConfig(plugin) {
|
|
|
128
129
|
files: [
|
|
129
130
|
"**/.github/workflows/*.yml",
|
|
130
131
|
"**/.github/workflows/*.yaml",
|
|
132
|
+
"**/.gitea/workflows/*.yml",
|
|
133
|
+
"**/.gitea/workflows/*.yaml",
|
|
131
134
|
"**/action.yml",
|
|
132
135
|
"**/action.yaml",
|
|
133
136
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@heroiclands/package-build",
|
|
3
|
-
"version": "23.1.
|
|
3
|
+
"version": "23.1.1",
|
|
4
4
|
"description": "Shared toolchain for building and shipping a HeroicLands Foundry VTT package — content compilation, manifest, localization, staging, bundle, release and deployment.",
|
|
5
5
|
"license": "GPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|
package/release.mjs
CHANGED
|
@@ -45,6 +45,60 @@ import { ZipArchive } from "archiver";
|
|
|
45
45
|
|
|
46
46
|
import { SCHEMA_ARTIFACT_FILE } from "./engine/foreign-catalog.mjs";
|
|
47
47
|
|
|
48
|
+
/** The earliest instant a zip entry's date field can hold. */
|
|
49
|
+
const ZIP_EPOCH_MS = Date.UTC(1980, 0, 1);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The timestamp stamped on every archive entry: `SOURCE_DATE_EPOCH` (seconds)
|
|
53
|
+
* when set to a number, otherwise 1980-01-01T00:00:00Z. A value earlier than
|
|
54
|
+
* 1980 is raised to it, which is as early as a zip can record.
|
|
55
|
+
*
|
|
56
|
+
* @returns {Date}
|
|
57
|
+
*/
|
|
58
|
+
function archiveDate() {
|
|
59
|
+
const seconds = Number(process.env.SOURCE_DATE_EPOCH);
|
|
60
|
+
const ms =
|
|
61
|
+
process.env.SOURCE_DATE_EPOCH && Number.isFinite(seconds) ? seconds * 1000 : ZIP_EPOCH_MS;
|
|
62
|
+
return new Date(Math.max(ms, ZIP_EPOCH_MS));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Append a directory tree to an archive in path order, with a fixed date and
|
|
67
|
+
* fixed permissions (0755 directories, 0644 files) so the bytes depend only on
|
|
68
|
+
* the tree's names and contents.
|
|
69
|
+
*
|
|
70
|
+
* Entries are appended one at a time from buffers: archiver's own directory
|
|
71
|
+
* walk stats files concurrently, so its entry order and recorded mtimes follow
|
|
72
|
+
* the filesystem.
|
|
73
|
+
*
|
|
74
|
+
* @param {import("archiver").ZipArchive} archive
|
|
75
|
+
* @param {string} root - The directory whose contents become the archive.
|
|
76
|
+
* @param {Date} date - The date recorded on every entry.
|
|
77
|
+
* @returns {Promise<void>}
|
|
78
|
+
*/
|
|
79
|
+
async function appendTree(archive, root, date) {
|
|
80
|
+
/** @param {string} rel - Path relative to `root`, `/`-separated, or "". */
|
|
81
|
+
async function walk(rel) {
|
|
82
|
+
const entries = await fsp.readdir(path.join(root, rel), { withFileTypes: true });
|
|
83
|
+
entries.sort((a, b) =>
|
|
84
|
+
a.name < b.name ? -1
|
|
85
|
+
: a.name > b.name ? 1
|
|
86
|
+
: 0,
|
|
87
|
+
);
|
|
88
|
+
for (const entry of entries) {
|
|
89
|
+
const name = rel ? `${rel}/${entry.name}` : entry.name;
|
|
90
|
+
if (entry.isDirectory()) {
|
|
91
|
+
archive.append(Buffer.alloc(0), { name: `${name}/`, date, mode: 0o755 });
|
|
92
|
+
await walk(name);
|
|
93
|
+
} else {
|
|
94
|
+
const body = await fsp.readFile(path.join(root, name));
|
|
95
|
+
archive.append(body, { name, date, mode: 0o644 });
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await walk("");
|
|
100
|
+
}
|
|
101
|
+
|
|
48
102
|
/**
|
|
49
103
|
* Zip the staged tree and place the manifest beside the archive.
|
|
50
104
|
*
|
|
@@ -55,6 +109,10 @@ import { SCHEMA_ARTIFACT_FILE } from "./engine/foreign-catalog.mjs";
|
|
|
55
109
|
* failure is timing-dependent, so it survives every run that happens to be
|
|
56
110
|
* fast enough, which is what makes it worth being explicit about.
|
|
57
111
|
*
|
|
112
|
+
* **The archive is reproducible.** Entries are written in path order with one
|
|
113
|
+
* date (`SOURCE_DATE_EPOCH` when set, otherwise 1980-01-01) and fixed
|
|
114
|
+
* permissions, so a rebuild of one staged tree is byte-identical.
|
|
115
|
+
*
|
|
58
116
|
* @param {object} [opts]
|
|
59
117
|
* @param {string} [opts.stageDir] - The staged package tree.
|
|
60
118
|
* @param {string} [opts.outDir] - Where the release assets are written.
|
|
@@ -114,10 +172,10 @@ export async function packRelease({
|
|
|
114
172
|
});
|
|
115
173
|
|
|
116
174
|
archive.pipe(output);
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
archive
|
|
175
|
+
// No top-level directory inside the zip: Foundry unpacks the archive
|
|
176
|
+
// *into* the package directory, so an extra level would nest the manifest
|
|
177
|
+
// one deeper than it looks for it.
|
|
178
|
+
await appendTree(archive, stage, archiveDate());
|
|
121
179
|
await archive.finalize();
|
|
122
180
|
await closed;
|
|
123
181
|
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
* - `no-empty-key`, `no-empty-document` — a fence or a file that parses to
|
|
23
23
|
* nothing at all.
|
|
24
24
|
*
|
|
25
|
-
* **
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
25
|
+
* **Workflows are exempt from `no-empty-mapping-value`**, under
|
|
26
|
+
* `.github/workflows/` and Gitea's `.gitea/workflows/` alike, because an empty
|
|
27
|
+
* value is the language there: `on:` `push:` and `workflow_dispatch:` carry
|
|
28
|
+
* their meaning by being present, and writing `push: null` to satisfy a linter
|
|
29
|
+
* would be worse YAML, not better.
|
|
29
30
|
*
|
|
30
31
|
* @param {object} plugin - The `eslint-plugin-yml` module.
|
|
31
32
|
* @returns {object[]} A complete flat config, for `overrideConfig`.
|
package/types/release.d.mts
CHANGED
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* failure is timing-dependent, so it survives every run that happens to be
|
|
9
9
|
* fast enough, which is what makes it worth being explicit about.
|
|
10
10
|
*
|
|
11
|
+
* **The archive is reproducible.** Entries are written in path order with one
|
|
12
|
+
* date (`SOURCE_DATE_EPOCH` when set, otherwise 1980-01-01) and fixed
|
|
13
|
+
* permissions, so a rebuild of one staged tree is byte-identical.
|
|
14
|
+
*
|
|
11
15
|
* @param {object} [opts]
|
|
12
16
|
* @param {string} [opts.stageDir] - The staged package tree.
|
|
13
17
|
* @param {string} [opts.outDir] - Where the release assets are written.
|