@hearthlog/tooling 0.1.1 → 0.1.3
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/bin/hearthlog.mjs +4 -2
- package/package.json +2 -2
- package/src/init/init.ts +1 -0
- package/src/tenants/lock-file.ts +4 -3
- package/src/tenants/module-lock.ts +11 -2
- package/templates/module/release.yml.tmpl +10 -4
- package/templates/tenant/dependabot.yml.tmpl +13 -0
- package/templates/tenant/github-workflows/deploy.yml.tmpl +4 -1
package/bin/hearthlog.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// The `hearthlog` command: runs the TypeScript tools through tsx. `build`, `init`, `export`, `check-module`, `artifact`, and
|
|
2
|
+
// The `hearthlog` command: runs the TypeScript tools through tsx. `build`, `init`, `export`, `check-module`, `lock`, `artifact`, and
|
|
3
3
|
// `gallery-public:build` start before a site has generated files (or write their own), so they run alone; the
|
|
4
4
|
// `apple:*` commands run the build and then the tools of `@hearthlog/native` (a dependency of the site, not of this
|
|
5
5
|
// package); `ensure`, `deploy`, `deploy-ledger`, and `split` run the build and then a deploy tool; every other
|
|
@@ -46,7 +46,8 @@ if (deployTools[command]) {
|
|
|
46
46
|
if (command === 'plan') {
|
|
47
47
|
const site = rest[rest.indexOf('--site') + 1];
|
|
48
48
|
const env = { ...process.env, ...(rest.includes('--site') && site ? { VITE_STOR_TENANT: site } : {}) };
|
|
49
|
-
|
|
49
|
+
// The build's messages go to stderr: `plan --json` writes only the plan to stdout.
|
|
50
|
+
const built = spawnSync(process.execPath, [tsx, source('generate/build-generated.ts'), '--quiet'], { stdio: ['inherit', 2, 'inherit'], env }).status ?? 1;
|
|
50
51
|
if (built !== 0) process.exit(built);
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -55,6 +56,7 @@ const entry = {
|
|
|
55
56
|
init: 'init/init.ts',
|
|
56
57
|
export: 'init/export-tenant.ts',
|
|
57
58
|
'check-module': 'init/check-module.ts',
|
|
59
|
+
lock: 'tenants/module-lock.ts',
|
|
58
60
|
'gallery-public:build': 'public-workers/build-gallery-public.ts',
|
|
59
61
|
artifact: 'deploy/artifact.ts',
|
|
60
62
|
}[command];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hearthlog/tooling",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "AGPL-3.0-only",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"tsx": "^4.23.15",
|
|
31
|
-
"@hearthlog/contracts": "0.1.
|
|
31
|
+
"@hearthlog/contracts": "0.1.3"
|
|
32
32
|
},
|
|
33
33
|
"bin": {
|
|
34
34
|
"hearthlog": "./bin/hearthlog.mjs"
|
package/src/init/init.ts
CHANGED
|
@@ -100,6 +100,7 @@ export function renderSite(options: InitOptions): Array<[string, string]> {
|
|
|
100
100
|
["pnpm-workspace.yaml", template("pnpm-workspace.yaml.tmpl")],
|
|
101
101
|
["patches/sqlocal@0.18.0.patch", template("patches/sqlocal@0.18.0.patch")],
|
|
102
102
|
[".github/workflows/deploy.yml", renderWorkflow(id)],
|
|
103
|
+
[".github/dependabot.yml", template("dependabot.yml.tmpl")],
|
|
103
104
|
[".gitignore", template("gitignore.tmpl")],
|
|
104
105
|
["README.md", render(template("README.md.tmpl"), values)],
|
|
105
106
|
];
|
package/src/tenants/lock-file.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The lock file's content: which modules a site ships and the exact manifest each was composed from. It
|
|
2
|
+
* The lock file's content: which modules a site ships and the exact manifest each was composed from. It names no version:
|
|
3
|
+
* a release that keeps a module's manifest changes nothing here (the package manager's lockfile adopts it), and a release
|
|
4
|
+
* that moves the contract changes `manifestSha256`, so `hearthlog lock --check` fails until the lock is regenerated and the
|
|
5
|
+
* diff names the module. It
|
|
3
6
|
* imports nothing generated, so `hearthlog init` can write one before a site has any generated files.
|
|
4
7
|
*/
|
|
5
8
|
|
|
@@ -14,7 +17,6 @@ import { modulesForFeatures, type LoadedModule } from "../compose/compose-module
|
|
|
14
17
|
export interface ModuleLockEntry {
|
|
15
18
|
id: string;
|
|
16
19
|
package: string;
|
|
17
|
-
version: string;
|
|
18
20
|
/** The site's enabled features this module provides. */
|
|
19
21
|
features: string[];
|
|
20
22
|
/** SHA-256 of the module's `hearthlog.module.json`. */
|
|
@@ -32,7 +34,6 @@ export function lockFor(modules: readonly LoadedModule[], features: readonly str
|
|
|
32
34
|
const entries: ModuleLockEntry[] = modulesForFeatures(modules, features).map(({ directory, manifest }) => ({
|
|
33
35
|
id: manifest.id,
|
|
34
36
|
package: manifest.package,
|
|
35
|
-
version: manifest.version,
|
|
36
37
|
features: manifest.features.map((feature) => feature.id).filter((id) => features.includes(id)),
|
|
37
38
|
manifestSha256: createHash("sha256").update(readFileSync(join(directory, "hearthlog.module.json"))).digest("hex"),
|
|
38
39
|
}));
|
|
@@ -33,8 +33,17 @@ export async function buildModuleLock(site: Pick<ResolvedSite, "features">): Pro
|
|
|
33
33
|
export async function moduleLockProblems(site: Pick<ResolvedSite, "id" | "features">): Promise<string[]> {
|
|
34
34
|
const expected = renderModuleLock(await buildModuleLock(site));
|
|
35
35
|
const current = await readFile(moduleLockPath(site.id), "utf8").catch(() => null);
|
|
36
|
-
if (current === null) return [`tenants/${site.id}/modules.lock.json is missing; run pnpm lock:modules`];
|
|
37
|
-
|
|
36
|
+
if (current === null) return [`tenants/${site.id}/modules.lock.json is missing; run pnpm lock:modules (hearthlog lock)`];
|
|
37
|
+
// Older locks also recorded each module's version; it is not part of the contract.
|
|
38
|
+
const withoutVersions = (text: string) => {
|
|
39
|
+
try {
|
|
40
|
+
const lock = JSON.parse(text) as { modules?: Array<Record<string, unknown>> };
|
|
41
|
+
return renderModuleLock({ ...lock, modules: (lock.modules ?? []).map(({ version: _version, ...entry }) => entry) } as unknown as ModuleLock);
|
|
42
|
+
} catch {
|
|
43
|
+
return text;
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
return withoutVersions(current) === expected ? [] : [`tenants/${site.id}/modules.lock.json is stale; run pnpm lock:modules (hearthlog lock)`];
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
name: Release
|
|
2
2
|
|
|
3
3
|
# Set "version" in package.json, commit, then push the tag `v<version>`. The publish job waits for approval in the
|
|
4
|
-
# protected `npm` environment; the
|
|
4
|
+
# protected `npm` environment; the package is published through npm's trusted publishing, so no registry token is stored.
|
|
5
5
|
on:
|
|
6
6
|
push:
|
|
7
7
|
tags: ["v*"]
|
|
@@ -37,10 +37,16 @@ jobs:
|
|
|
37
37
|
with:
|
|
38
38
|
node-version: 24
|
|
39
39
|
registry-url: https://registry.npmjs.org
|
|
40
|
+
# npm's trusted publishing (OIDC) needs npm 11.5.1 or later; no token is stored anywhere.
|
|
41
|
+
- run: npm install --global npm@11
|
|
40
42
|
- run: pnpm install
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
- name: Publish the version in package.json unless the registry has it
|
|
44
|
+
run: |
|
|
45
|
+
name=$(node -p "require('./package.json').name")
|
|
46
|
+
version=$(node -p "require('./package.json').version")
|
|
47
|
+
if [ "$(npm view "$name@$version" version 2>/dev/null)" = "$version" ]; then echo "$name@$version is already published"; exit 0; fi
|
|
48
|
+
pnpm pack --pack-destination "$RUNNER_TEMP/pack"
|
|
49
|
+
npm publish "$RUNNER_TEMP/pack/"*.tgz --access public --provenance
|
|
44
50
|
|
|
45
51
|
release:
|
|
46
52
|
needs: publish
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Releases of the platform and of the modules arrive as pull requests that change the lockfile (and nothing else, unless a
|
|
2
|
+
# module's contract moved: then `hearthlog lock` rewrites tenants/<id>/modules.lock.json and the diff names the module).
|
|
3
|
+
# The build job of this repository's workflow builds each one; merge it to deploy, with the approval of the environment.
|
|
4
|
+
version: 2
|
|
5
|
+
updates:
|
|
6
|
+
- package-ecosystem: npm
|
|
7
|
+
directory: "/"
|
|
8
|
+
schedule:
|
|
9
|
+
interval: weekly
|
|
10
|
+
groups:
|
|
11
|
+
hearthlog:
|
|
12
|
+
patterns:
|
|
13
|
+
- "@hearthlog/*"
|
|
@@ -46,7 +46,10 @@ jobs:
|
|
|
46
46
|
pnpm exec hearthlog gallery-public:build --site {{id}}
|
|
47
47
|
pnpm exec vite build
|
|
48
48
|
pnpm exec hearthlog artifact manifest --site {{id}}
|
|
49
|
-
# After the build: the plan
|
|
49
|
+
# After the build: the lock and the plan read the generated files the build writes. The lock fails when a module's
|
|
50
|
+
# contract changed since `tenants/{{id}}/modules.lock.json` was written: run `pnpm exec hearthlog lock` and commit it.
|
|
51
|
+
- name: Check the module lock
|
|
52
|
+
run: pnpm exec hearthlog lock --check
|
|
50
53
|
- name: Plan (read-only)
|
|
51
54
|
run: pnpm exec hearthlog plan --site {{id}}
|
|
52
55
|
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|