@hearthlog/tooling 0.0.0-stage → 0.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/LICENSE +235 -0
- package/bin/hearthlog.mjs +61 -0
- package/package.json +64 -3
- package/src/apply.ts +73 -0
- package/src/check/check-layers.ts +118 -0
- package/src/check/check-modules.ts +198 -0
- package/src/check/check-sdk-exports.ts +79 -0
- package/src/check/check-tenant-config.ts +208 -0
- package/src/cli/hearthlog.ts +212 -0
- package/src/cli/list-sites.ts +15 -0
- package/src/compose/compose-modules.ts +490 -0
- package/src/compose/module-scaffold.ts +195 -0
- package/src/deploy/artifact-compare.ts +93 -0
- package/src/deploy/artifact.ts +127 -0
- package/src/deploy/async-utils.ts +71 -0
- package/src/deploy/backup-tenants.ts +139 -0
- package/src/deploy/cloudflare-access.ts +789 -0
- package/src/deploy/copy-tenant.ts +395 -0
- package/src/deploy/dedicated-ledger.ts +77 -0
- package/src/deploy/deploy-ledger.ts +162 -0
- package/src/deploy/deploy-runner.ts +240 -0
- package/src/deploy/deploy-tenant.ts +170 -0
- package/src/deploy/deployment-security.ts +141 -0
- package/src/deploy/ensure-tenant.ts +988 -0
- package/src/deploy/event-policy.ts +23 -0
- package/src/deploy/frontend-dry-run.ts +18 -0
- package/src/deploy/ledger-credential.ts +48 -0
- package/src/deploy/ledger-scripts.ts +13 -0
- package/src/deploy/split-tenant.ts +295 -0
- package/src/dev/local-development.ts +288 -0
- package/src/dev/provision-local.ts +13 -0
- package/src/generate/build-generated.ts +166 -0
- package/src/generate/generate-brand-icons.ts +128 -0
- package/src/generate/generate-entities.ts +627 -0
- package/src/generate/generate-tenant.ts +161 -0
- package/src/host/core-routes.ts +39 -0
- package/src/host/write-host.ts +119 -0
- package/src/index.ts +8 -0
- package/src/init/check-module.ts +143 -0
- package/src/init/export-tenant.ts +124 -0
- package/src/init/init.ts +168 -0
- package/src/move.ts +189 -0
- package/src/plan.ts +160 -0
- package/src/public-workers/build-gallery-public.ts +114 -0
- package/src/public-workers/calendar-public-config.ts +27 -0
- package/src/public-workers/check-calendar-public-route.ts +41 -0
- package/src/public-workers/check-gallery-public-route.ts +43 -0
- package/src/public-workers/gallery-public-config.ts +22 -0
- package/src/receipt.ts +74 -0
- package/src/root.ts +16 -0
- package/src/security/audit-push.ts +141 -0
- package/src/security/forbidden-identities.ts +107 -0
- package/src/security/forbidden-init.ts +88 -0
- package/src/tenants/host-configs.ts +96 -0
- package/src/tenants/ledger-config.ts +57 -0
- package/src/tenants/lock-file.ts +45 -0
- package/src/tenants/module-lock.ts +58 -0
- package/src/tenants/tenant-config.ts +887 -0
- package/src/tenants/tenant-copy-plan.ts +119 -0
- package/src/tenants/tenant-schema.ts +171 -0
- package/src/tenants/tenant-wrangler.ts +37 -0
- package/src/tenants/wrangler-base.ts +50 -0
- package/src/tenants/wrangler-config.ts +266 -0
- package/src/vite/config.ts +310 -0
- package/src/vite/dev-plugins.ts +338 -0
- package/src/vite/vite-plugin-entity-gen.ts +70 -0
- package/src/vite/vite-plugin-tenant-manifest.ts +61 -0
- package/src/vite/vite-plugin-worker-assets.ts +42 -0
- package/templates/host/app.d.ts.txt +31 -0
- package/templates/host/microlighter.d.ts.txt +11 -0
- package/templates/module/ci.yml.tmpl +44 -0
- package/templates/module/gitignore.tmpl +7 -0
- package/templates/module/gitleaks.toml.tmpl +11 -0
- package/templates/module/release.yml.tmpl +53 -0
- package/templates/tenant/README.md.tmpl +13 -0
- package/templates/tenant/generated-package.json.tmpl +16 -0
- package/templates/tenant/github-workflows/deploy.yml.tmpl +111 -0
- package/templates/tenant/gitignore.tmpl +11 -0
- package/templates/tenant/package.json.tmpl +11 -0
- package/templates/tenant/patches/sqlocal@0.18.0.patch +18 -0
- package/templates/tenant/pnpm-workspace.yaml.tmpl +8 -0
- package/templates/tenant/tenants/__id__/tenant.json.tmpl +8 -0
- package/templates/tenant/tenants.json.tmpl +17 -0
- package/templates/tenant/tsconfig.json.tmpl +21 -0
- package/templates/tenant/vite.config.ts.tmpl +5 -0
- package/README.md +0 -3
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin: Entity Generator
|
|
3
|
+
*
|
|
4
|
+
* Watches entity-definitions.ts and auto-regenerates types on change.
|
|
5
|
+
* This provides a better DX by eliminating the need to manually run
|
|
6
|
+
* `pnpm generate:entities` during development.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Plugin } from 'vite';
|
|
10
|
+
import { spawn } from 'child_process';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
|
|
13
|
+
let isGenerating = false;
|
|
14
|
+
|
|
15
|
+
export function entityGeneratorPlugin(): Plugin {
|
|
16
|
+
return {
|
|
17
|
+
name: 'entity-generator',
|
|
18
|
+
configureServer(server) {
|
|
19
|
+
const entityDefsPath = path.resolve('packages/contracts/src/event/entity-definitions.ts');
|
|
20
|
+
|
|
21
|
+
// Add the file to the watcher
|
|
22
|
+
server.watcher.add(entityDefsPath);
|
|
23
|
+
|
|
24
|
+
server.watcher.on('change', (changedPath) => {
|
|
25
|
+
// Normalize paths for comparison
|
|
26
|
+
const normalizedChanged = path.normalize(changedPath);
|
|
27
|
+
const normalizedTarget = path.normalize(entityDefsPath);
|
|
28
|
+
|
|
29
|
+
if (normalizedChanged === normalizedTarget && !isGenerating) {
|
|
30
|
+
isGenerating = true;
|
|
31
|
+
console.log('\n[entity-gen] 🔄 Regenerating entity types...');
|
|
32
|
+
|
|
33
|
+
const startTime = Date.now();
|
|
34
|
+
const child = spawn('pnpm', ['generate:entities'], {
|
|
35
|
+
stdio: 'pipe',
|
|
36
|
+
shell: true,
|
|
37
|
+
cwd: process.cwd()
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
child.stdout?.on('data', (data) => {
|
|
41
|
+
const output = data.toString().trim();
|
|
42
|
+
if (output) console.log(`[entity-gen] ${output}`);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
child.stderr?.on('data', (data) => {
|
|
46
|
+
const output = data.toString().trim();
|
|
47
|
+
if (output) console.error(`[entity-gen] ❌ ${output}`);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
child.on('close', (code) => {
|
|
51
|
+
const elapsed = Date.now() - startTime;
|
|
52
|
+
if (code === 0) {
|
|
53
|
+
console.log(`[entity-gen] ✅ Done in ${elapsed}ms`);
|
|
54
|
+
} else {
|
|
55
|
+
console.error(`[entity-gen] ❌ Failed with code ${code}`);
|
|
56
|
+
}
|
|
57
|
+
isGenerating = false;
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
child.on('error', (err) => {
|
|
61
|
+
console.error(`[entity-gen] ❌ Failed to start: ${err.message}`);
|
|
62
|
+
isGenerating = false;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
console.log('[entity-gen] 👀 Watching entity-definitions.ts for changes');
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin: Tenant Web App Manifest
|
|
3
|
+
*
|
|
4
|
+
* Serves and emits /manifest.webmanifest for the tenant selected with
|
|
5
|
+
* VITE_STOR_TENANT, so each installed app gets its own name, theme colour, and
|
|
6
|
+
* icon set (tenants/<site-id>/static/icons/<site-id>/, see packages/tooling/src/generate/generate-brand-icons.ts).
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Plugin } from 'vite';
|
|
10
|
+
|
|
11
|
+
import { themeVariablesFor } from '../../../contracts/src/tenant/theme.js';
|
|
12
|
+
import { loadTenantConfig, siteById } from '../tenants/tenant-config.js';
|
|
13
|
+
|
|
14
|
+
const MANIFEST_PATH = '/manifest.webmanifest';
|
|
15
|
+
|
|
16
|
+
async function manifestJson(siteId: string): Promise<string> {
|
|
17
|
+
const { sites } = await loadTenantConfig(undefined, { only: siteId });
|
|
18
|
+
const site = siteById(sites, siteId);
|
|
19
|
+
const color = themeVariablesFor(site.theme.accent, 'light')['--stor-theme-color'];
|
|
20
|
+
const icons = `/icons/${site.id}`;
|
|
21
|
+
return JSON.stringify(
|
|
22
|
+
{
|
|
23
|
+
name: site.title,
|
|
24
|
+
short_name: site.title,
|
|
25
|
+
id: site.title.trim().toLowerCase(),
|
|
26
|
+
start_url: '/',
|
|
27
|
+
scope: '/',
|
|
28
|
+
display: 'standalone',
|
|
29
|
+
theme_color: color,
|
|
30
|
+
background_color: color,
|
|
31
|
+
icons: [
|
|
32
|
+
{ src: `${icons}/pwa-192.png`, sizes: '192x192', type: 'image/png', purpose: 'any' },
|
|
33
|
+
{ src: `${icons}/pwa-512.png`, sizes: '512x512', type: 'image/png', purpose: 'any' },
|
|
34
|
+
{ src: `${icons}/maskable-512.png`, sizes: '512x512', type: 'image/png', purpose: 'maskable' },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
null,
|
|
38
|
+
2,
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function tenantManifestPlugin(): Plugin {
|
|
43
|
+
const siteId = process.env.VITE_STOR_TENANT?.trim() || 'st';
|
|
44
|
+
return {
|
|
45
|
+
name: 'tenant-manifest',
|
|
46
|
+
configureServer(server) {
|
|
47
|
+
server.middlewares.use(MANIFEST_PATH, async (_request, response) => {
|
|
48
|
+
response.setHeader('Content-Type', 'application/manifest+json; charset=utf-8');
|
|
49
|
+
response.end(await manifestJson(siteId));
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
async generateBundle() {
|
|
53
|
+
if (this.environment?.name !== 'client') return;
|
|
54
|
+
this.emitFile({
|
|
55
|
+
type: 'asset',
|
|
56
|
+
fileName: MANIFEST_PATH.slice(1),
|
|
57
|
+
source: await manifestJson(siteId),
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vite Plugin: Worker asset list for the service worker
|
|
3
|
+
*
|
|
4
|
+
* SvelteKit's `$app/manifest` lists the worker entry files but not the chunks
|
|
5
|
+
* and WASM they load, because those are copied into the client output after the
|
|
6
|
+
* service worker is bundled. The SQLite WASM worker needs all of them offline,
|
|
7
|
+
* so `virtual:worker-assets` lists everything Kit emitted under
|
|
8
|
+
* `_app/immutable/workers`.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
12
|
+
import { join, relative } from 'node:path';
|
|
13
|
+
|
|
14
|
+
import type { Plugin } from 'vite';
|
|
15
|
+
|
|
16
|
+
const VIRTUAL_ID = 'virtual:worker-assets';
|
|
17
|
+
const RESOLVED_ID = `\0${VIRTUAL_ID}`;
|
|
18
|
+
|
|
19
|
+
function listFiles(directory: string): string[] {
|
|
20
|
+
if (!existsSync(directory)) return [];
|
|
21
|
+
return readdirSync(directory, { withFileTypes: true }).flatMap((entry) => {
|
|
22
|
+
const path = join(directory, entry.name);
|
|
23
|
+
return entry.isDirectory() ? listFiles(path) : [path];
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function workerAssetsPlugin(outDir: string): Plugin {
|
|
28
|
+
const clientOutput = join(outDir, 'output', 'client');
|
|
29
|
+
return {
|
|
30
|
+
name: 'worker-assets',
|
|
31
|
+
resolveId(id) {
|
|
32
|
+
return id === VIRTUAL_ID ? RESOLVED_ID : undefined;
|
|
33
|
+
},
|
|
34
|
+
load(id) {
|
|
35
|
+
if (id !== RESOLVED_ID) return undefined;
|
|
36
|
+
const paths = listFiles(join(clientOutput, '_app', 'immutable', 'workers'))
|
|
37
|
+
.map((file) => ({ path: relative(clientOutput, file).split('\\').join('/') }))
|
|
38
|
+
.sort((left, right) => left.path.localeCompare(right.path));
|
|
39
|
+
return `export const workerAssets = ${JSON.stringify(paths)};`;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import 'unplugin-icons/types/svelte';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace App { }
|
|
5
|
+
const __BUILD_TIMESTAMP__: string;
|
|
6
|
+
const __STACK_INFO__: {
|
|
7
|
+
packages: Record<string, string>;
|
|
8
|
+
node: string;
|
|
9
|
+
commit: string;
|
|
10
|
+
branch: string;
|
|
11
|
+
dirty: boolean;
|
|
12
|
+
};
|
|
13
|
+
interface Window {
|
|
14
|
+
__storE2E?: import('@hearthlog/runtime/testing/e2e-seed.js').StorE2ESeedApi;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface ImportMetaEnv {
|
|
19
|
+
readonly VITE_LEDGER_SERVICE_URL?: string;
|
|
20
|
+
readonly VITE_LEDGER_AUTH_TOKEN?: string;
|
|
21
|
+
readonly VITE_LEDGER_TIMEOUT_MS?: string;
|
|
22
|
+
readonly VITE_LEDGER_MAX_RETRIES?: string;
|
|
23
|
+
readonly VITE_LEDGER_ENABLE_REALTIME?: string;
|
|
24
|
+
readonly VITE_E2E?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface ImportMeta {
|
|
28
|
+
readonly env: ImportMetaEnv;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export { };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
push:
|
|
6
|
+
branches: [main]
|
|
7
|
+
|
|
8
|
+
permissions:
|
|
9
|
+
contents: read
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ci-${{ github.ref }}
|
|
13
|
+
cancel-in-progress: true
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
test:
|
|
17
|
+
name: Builds into a site, unit tests
|
|
18
|
+
runs-on: ubuntu-latest
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
21
|
+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
22
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
23
|
+
with:
|
|
24
|
+
node-version: 24
|
|
25
|
+
- run: pnpm install
|
|
26
|
+
# Generates a site that enables this module against the platform range, installs and builds it, then runs the
|
|
27
|
+
# module's unit tests against that site's generated files.
|
|
28
|
+
- run: pnpm test
|
|
29
|
+
|
|
30
|
+
secrets:
|
|
31
|
+
name: Secret scan
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
35
|
+
with:
|
|
36
|
+
fetch-depth: 0
|
|
37
|
+
- name: Install gitleaks
|
|
38
|
+
run: |
|
|
39
|
+
version=8.30.1
|
|
40
|
+
curl -sSfL -o gitleaks.tar.gz "https://github.com/gitleaks/gitleaks/releases/download/v${version}/gitleaks_${version}_linux_x64.tar.gz"
|
|
41
|
+
echo "551f6fc83ea457d62a0d98237cbad105af8d557003051f41f3e7ca7b3f2470eb gitleaks.tar.gz" | sha256sum --check
|
|
42
|
+
tar xzf gitleaks.tar.gz gitleaks
|
|
43
|
+
- run: ./gitleaks git --redact --no-banner --verbose .
|
|
44
|
+
- run: ./gitleaks dir --redact --no-banner --verbose .
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
title = "hearthlog"
|
|
2
|
+
|
|
3
|
+
[extend]
|
|
4
|
+
useDefault = true
|
|
5
|
+
|
|
6
|
+
# Entity definitions name the fields of an event payload (`payloadKey: "caloriesPer100g"`); a field name that ends in
|
|
7
|
+
# digits and a letter looks like a key to the generic rule, but it is not a credential.
|
|
8
|
+
[[allowlists]]
|
|
9
|
+
description = "payloadKey names a payload field"
|
|
10
|
+
regexTarget = "line"
|
|
11
|
+
regexes = ['''payloadKey:\s*["'][A-Za-z0-9_]+["']''']
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
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.
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags: ["v*"]
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
verify:
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
17
|
+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
18
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
19
|
+
with:
|
|
20
|
+
node-version: 24
|
|
21
|
+
- name: The tag names the version in package.json
|
|
22
|
+
run: test "$GITHUB_REF_NAME" = "v$(node -p "require('./package.json').version")"
|
|
23
|
+
- run: pnpm install
|
|
24
|
+
- run: pnpm test
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: verify
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment: npm
|
|
30
|
+
permissions:
|
|
31
|
+
contents: read
|
|
32
|
+
id-token: write # provenance
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
35
|
+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
36
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
37
|
+
with:
|
|
38
|
+
node-version: 24
|
|
39
|
+
registry-url: https://registry.npmjs.org
|
|
40
|
+
- run: pnpm install
|
|
41
|
+
- run: pnpm publish --no-git-checks
|
|
42
|
+
env:
|
|
43
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
44
|
+
|
|
45
|
+
release:
|
|
46
|
+
needs: publish
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
permissions:
|
|
49
|
+
contents: write
|
|
50
|
+
steps:
|
|
51
|
+
- run: gh release create "$GITHUB_REF_NAME" --repo "$GITHUB_REPOSITORY" --title "$GITHUB_REF_NAME" --generate-notes
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ github.token }}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# {{title}}
|
|
2
|
+
|
|
3
|
+
A Hearthlog site. `tenants/{{id}}/tenant.json` says what it enables, `tenants/{{id}}/modules.lock.json` records the
|
|
4
|
+
modules it ships, and everything else the app needs is written by `pnpm build:generated` into `.generated/`.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
pnpm install
|
|
8
|
+
pnpm dev # builds the generated files, then starts Vite
|
|
9
|
+
pnpm build # a static build in build/
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`tenants.json` holds placeholder infrastructure names: it makes the site load, and it cannot be deployed until real
|
|
13
|
+
values replace them.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hearthlog/generated",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./schema": "./src/schema.generated.ts",
|
|
8
|
+
"./types": "./src/types.generated.ts",
|
|
9
|
+
"./standard-commands": "./src/standard-commands.generated.ts",
|
|
10
|
+
"./standard-db": "./src/standard-db.generated.ts",
|
|
11
|
+
"./module-repositories": "./src/module-repositories.generated.ts",
|
|
12
|
+
"./modules": "./src/modules.generated.ts",
|
|
13
|
+
"./module-icons": "./src/module-icons.generated.ts",
|
|
14
|
+
"./event-schemas": "./src/event-schemas.generated.ts"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
name: Deploy {{id}}
|
|
2
|
+
|
|
3
|
+
# Two jobs. `build` has no secrets: it installs the locked dependencies, builds the site, and seals the result in
|
|
4
|
+
# artifact-manifest.json. `deploy` runs in the protected `{{id}}` environment, so it starts only once a reviewer
|
|
5
|
+
# approves, and it is the only job that holds secrets. It installs the same lockfile with scripts disabled, checks the
|
|
6
|
+
# manifest, and deploys what `build` made. It never creates a Ledger token or signing key: provision the stack once
|
|
7
|
+
# on your machine with `hearthlog apply`. Each run uploads its receipt (no secrets) as an artifact.
|
|
8
|
+
#
|
|
9
|
+
# In the `{{id}}` environment: CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, and LEDGER_ADMIN_TOKEN (the administrator
|
|
10
|
+
# token of this site's Ledger); optionally INVOICE_RENDERER_TOKEN, VERTEX_API_KEY, and VERTEX_SERVICE_ACCOUNT.
|
|
11
|
+
|
|
12
|
+
on:
|
|
13
|
+
pull_request:
|
|
14
|
+
push:
|
|
15
|
+
branches: [main]{{pushPaths}}
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
inputs:
|
|
18
|
+
dry_run:
|
|
19
|
+
description: Validate and bundle without uploading
|
|
20
|
+
type: boolean
|
|
21
|
+
default: false
|
|
22
|
+
|
|
23
|
+
permissions:
|
|
24
|
+
contents: read
|
|
25
|
+
|
|
26
|
+
jobs:
|
|
27
|
+
build:
|
|
28
|
+
name: Build {{id}}
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
32
|
+
with:
|
|
33
|
+
persist-credentials: false
|
|
34
|
+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
35
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
36
|
+
with:
|
|
37
|
+
node-version: 24
|
|
38
|
+
cache: pnpm
|
|
39
|
+
- name: Install dependencies
|
|
40
|
+
run: pnpm install --frozen-lockfile
|
|
41
|
+
- name: Build and seal
|
|
42
|
+
env:
|
|
43
|
+
VITE_STOR_TENANT: {{id}}
|
|
44
|
+
run: |
|
|
45
|
+
pnpm exec hearthlog build
|
|
46
|
+
pnpm exec hearthlog gallery-public:build --site {{id}}
|
|
47
|
+
pnpm exec vite build
|
|
48
|
+
pnpm exec hearthlog artifact manifest --site {{id}}
|
|
49
|
+
# After the build: the plan reads the generated files the build writes.
|
|
50
|
+
- name: Plan (read-only)
|
|
51
|
+
run: pnpm exec hearthlog plan --site {{id}}
|
|
52
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
53
|
+
with:
|
|
54
|
+
name: release
|
|
55
|
+
path: |
|
|
56
|
+
build
|
|
57
|
+
.generated
|
|
58
|
+
artifact-manifest.json
|
|
59
|
+
include-hidden-files: true
|
|
60
|
+
if-no-files-found: error
|
|
61
|
+
retention-days: 14
|
|
62
|
+
|
|
63
|
+
deploy:
|
|
64
|
+
name: Deploy {{id}}
|
|
65
|
+
needs: build
|
|
66
|
+
if: github.event_name != 'pull_request'
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
environment: {{id}}
|
|
69
|
+
concurrency:
|
|
70
|
+
group: deploy-{{id}}
|
|
71
|
+
cancel-in-progress: false
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
|
|
74
|
+
with:
|
|
75
|
+
persist-credentials: false
|
|
76
|
+
- uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v4.4.0
|
|
77
|
+
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
|
|
78
|
+
with:
|
|
79
|
+
node-version: 24
|
|
80
|
+
cache: pnpm
|
|
81
|
+
# No lifecycle script of any dependency runs in the job that holds the secrets.
|
|
82
|
+
- name: Install dependencies
|
|
83
|
+
run: pnpm install --frozen-lockfile --ignore-scripts
|
|
84
|
+
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
|
85
|
+
with:
|
|
86
|
+
name: release
|
|
87
|
+
path: .
|
|
88
|
+
- name: Check the release against its manifest
|
|
89
|
+
run: pnpm exec hearthlog artifact verify --site {{id}}
|
|
90
|
+
- name: Deploy
|
|
91
|
+
env:
|
|
92
|
+
DRY_RUN: ${{ github.event.inputs.dry_run }}
|
|
93
|
+
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
94
|
+
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
95
|
+
LEDGER_ADMIN_TOKEN_{{idUpper}}: ${{ secrets.LEDGER_ADMIN_TOKEN }}
|
|
96
|
+
INVOICE_RENDERER_TOKEN: ${{ secrets.INVOICE_RENDERER_TOKEN }}
|
|
97
|
+
INVOICE_EXTRACTION_API_KEY: ${{ secrets.VERTEX_API_KEY }}
|
|
98
|
+
INVOICE_EXTRACTION_SERVICE_ACCOUNT: ${{ secrets.VERTEX_SERVICE_ACCOUNT }}
|
|
99
|
+
run: |
|
|
100
|
+
set -euo pipefail
|
|
101
|
+
args=(--site {{id}} --yes --skip-build --no-bootstrap)
|
|
102
|
+
if [ "$DRY_RUN" = "true" ]; then args+=(--dry-run); fi
|
|
103
|
+
pnpm exec hearthlog apply "${args[@]}"
|
|
104
|
+
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
105
|
+
if: always()
|
|
106
|
+
with:
|
|
107
|
+
name: receipt
|
|
108
|
+
path: .wrangler/tenants/{{id}}/receipt.json
|
|
109
|
+
include-hidden-files: true
|
|
110
|
+
if-no-files-found: ignore
|
|
111
|
+
retention-days: 90
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# What `hearthlog build` writes; only .generated/package.json is committed.
|
|
2
|
+
/.generated/*
|
|
3
|
+
!/.generated/package.json
|
|
4
|
+
/.svelte-kit/
|
|
5
|
+
/build/
|
|
6
|
+
# The seal of a build (`hearthlog artifact manifest`): it travels with the release artifact, not with the source.
|
|
7
|
+
/artifact-manifest.json
|
|
8
|
+
node_modules/
|
|
9
|
+
# Native apps (`hearthlog apple:*`): build output and personal device settings
|
|
10
|
+
/.apple-build/
|
|
11
|
+
/resign.local.json
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"private": true,
|
|
3
|
+
"type": "module",
|
|
4
|
+
"packageManager": "pnpm@10.32.1",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"build:generated": "hearthlog build",
|
|
7
|
+
"dev": "hearthlog build && vite dev",
|
|
8
|
+
"build": "hearthlog build && vite build",
|
|
9
|
+
"check": "hearthlog build && svelte-kit sync && tsc --noEmit -p tsconfig.json --skipLibCheck"
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
diff --git a/dist/processor.js b/dist/processor.js
|
|
2
|
+
--- a/dist/processor.js
|
|
3
|
+
+++ b/dist/processor.js
|
|
4
|
+
@@ -453,12 +453,13 @@ export class SQLocalProcessor {
|
|
5
|
+
const { queryKey } = message;
|
|
6
|
+
try {
|
|
7
|
+
const { name, data } = await this.driver.export();
|
|
8
|
+
+ const transfer = data instanceof ArrayBuffer ? data : data.buffer;
|
|
9
|
+
this.emitMessage({
|
|
10
|
+
type: 'buffer',
|
|
11
|
+
queryKey,
|
|
12
|
+
bufferName: name,
|
|
13
|
+
buffer: data,
|
|
14
|
+
- }, [data]);
|
|
15
|
+
+ }, [transfer]);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
this.emitMessage({
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"infrastructure": {
|
|
3
|
+
"ledgerWorker": "example-ledger",
|
|
4
|
+
"invoicePdfsBucket": "example-invoice-pdfs",
|
|
5
|
+
"calendarFeedsBucket": "example-calendar-feeds",
|
|
6
|
+
"calendarPublicWorker": "example-calendar-public",
|
|
7
|
+
"calendarPublicOrigin": "https://example-calendar-public.example.workers.dev",
|
|
8
|
+
"galleryFilesBucket": "example-gallery-files",
|
|
9
|
+
"galleryPublicBucket": "example-gallery-public",
|
|
10
|
+
"galleryPublicWorker": "example-gallery-public",
|
|
11
|
+
"galleryPublicOrigin": "https://example-gallery-public.example.workers.dev",
|
|
12
|
+
"workersDevDomain": "example.workers.dev",
|
|
13
|
+
"ledgerAdminEmail": "owner@example.com",
|
|
14
|
+
"accessTeamDomain": "https://example.cloudflareaccess.com"
|
|
15
|
+
},
|
|
16
|
+
"sites": []
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": ["$app/tsconfig", "./.generated/tsconfig.json"],
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"strict": true,
|
|
5
|
+
"noUnusedLocals": true,
|
|
6
|
+
"types": ["node", "$app/types"]
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
".svelte-kit/ambient.d.ts",
|
|
10
|
+
".svelte-kit/env.d.ts",
|
|
11
|
+
".svelte-kit/non-ambient.d.ts",
|
|
12
|
+
".svelte-kit/types/**/$types.d.ts",
|
|
13
|
+
"vite.config.ts",
|
|
14
|
+
".generated/routes/**/*.ts",
|
|
15
|
+
".generated/routes/**/*.svelte",
|
|
16
|
+
".generated/*.d.ts",
|
|
17
|
+
"src/**/*.ts",
|
|
18
|
+
"src/**/*.svelte"
|
|
19
|
+
],
|
|
20
|
+
"exclude": ["node_modules/**"]
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
// The site's Vite configuration is the shared one; the import is a relative path because Vite's config
|
|
2
|
+
// loader cannot load TypeScript from a package by name.
|
|
3
|
+
import { hearthlogConfig } from './node_modules/@hearthlog/tooling/src/vite/config.ts';
|
|
4
|
+
|
|
5
|
+
export default hearthlogConfig();
|
package/README.md
DELETED