@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 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
- const built = spawnSync(process.execPath, [tsx, source('generate/build-generated.ts'), '--quiet'], { stdio: 'inherit', env }).status ?? 1;
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.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.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
  ];
@@ -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
- return current === expected ? [] : [`tenants/${site.id}/modules.lock.json is stale; run pnpm lock:modules`];
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 registry credential is the organization secret NPM_TOKEN.
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
- - run: pnpm publish --no-git-checks
42
- env:
43
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
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 reads the generated files the build writes.
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