looply-cli 0.5.13 → 0.5.17

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.
Files changed (44) hide show
  1. package/dist/.src-hash +1 -1
  2. package/dist/bin.d.ts.map +1 -1
  3. package/dist/bin.js +8 -0
  4. package/dist/bin.js.map +1 -1
  5. package/dist/config.d.ts +19 -1
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +29 -0
  8. package/dist/config.js.map +1 -1
  9. package/dist/daemon-cmd.d.ts.map +1 -1
  10. package/dist/daemon-cmd.js +18 -0
  11. package/dist/daemon-cmd.js.map +1 -1
  12. package/dist/heroku-api.d.ts +87 -0
  13. package/dist/heroku-api.d.ts.map +1 -0
  14. package/dist/heroku-api.js +178 -0
  15. package/dist/heroku-api.js.map +1 -0
  16. package/dist/preview-cmd.d.ts +51 -0
  17. package/dist/preview-cmd.d.ts.map +1 -0
  18. package/dist/preview-cmd.js +194 -0
  19. package/dist/preview-cmd.js.map +1 -0
  20. package/dist/preview-environments.d.ts +173 -0
  21. package/dist/preview-environments.d.ts.map +1 -0
  22. package/dist/preview-environments.js +482 -0
  23. package/dist/preview-environments.js.map +1 -0
  24. package/dist/preview-github.d.ts +63 -0
  25. package/dist/preview-github.d.ts.map +1 -0
  26. package/dist/preview-github.js +130 -0
  27. package/dist/preview-github.js.map +1 -0
  28. package/dist/preview-settings-cache.d.ts +33 -0
  29. package/dist/preview-settings-cache.d.ts.map +1 -0
  30. package/dist/preview-settings-cache.js +108 -0
  31. package/dist/preview-settings-cache.js.map +1 -0
  32. package/dist/preview-wiring.d.ts +35 -0
  33. package/dist/preview-wiring.d.ts.map +1 -0
  34. package/dist/preview-wiring.js +81 -0
  35. package/dist/preview-wiring.js.map +1 -0
  36. package/dist/run-spend-report.d.ts +55 -0
  37. package/dist/run-spend-report.d.ts.map +1 -0
  38. package/dist/run-spend-report.js +84 -0
  39. package/dist/run-spend-report.js.map +1 -0
  40. package/dist/server-client.d.ts +22 -0
  41. package/dist/server-client.d.ts.map +1 -1
  42. package/dist/server-client.js +26 -0
  43. package/dist/server-client.js.map +1 -1
  44. package/package.json +2 -2
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * WHAT A PROVIDER ALREADY SAID ABOUT A PULL REQUEST'S PREVIEW, READ FROM GITHUB.
4
+ *
5
+ * Netlify and Heroku both report their preview builds back to GitHub on the
6
+ * pull request's head commit — Netlify as a commit status
7
+ * (`netlify/<site>/deploy-preview`, its `target_url` the preview) and as a
8
+ * deployment; Heroku review apps as a deployment whose status carries the app's
9
+ * `environment_url`. Reading those means Looply needs no Netlify credentials at
10
+ * all and no Heroku ones to FIND a preview: it is the provider's own mechanism,
11
+ * observed.
12
+ *
13
+ * Read-only: GET only, with the token `github-api.ts` already uses for pull
14
+ * requests. One write-free exception, `sourceTarballUrl`, asks GitHub for the
15
+ * short-lived download link of a commit's source — a GET that is answered with a
16
+ * redirect — which Heroku needs to build a review app Looply asked for.
17
+ */
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.makeGitHubPreviewReader = makeGitHubPreviewReader;
20
+ exports.netlifySignal = netlifySignal;
21
+ exports.herokuSignal = herokuSignal;
22
+ function makeGitHubPreviewReader(deps) {
23
+ const doFetch = deps.fetch ?? ((url, init) => fetch(url, init));
24
+ const base = `https://api.github.com/repos/${encodeURIComponent(deps.owner)}/${encodeURIComponent(deps.repo)}`;
25
+ const headers = {
26
+ authorization: `Bearer ${deps.token}`,
27
+ accept: 'application/vnd.github+json',
28
+ 'x-github-api-version': '2022-11-28',
29
+ 'user-agent': 'looply-cli',
30
+ };
31
+ async function get(path) {
32
+ const res = await doFetch(`${base}${path}`, { method: 'GET', headers });
33
+ const text = await res.text();
34
+ if (res.status === 404)
35
+ return null;
36
+ if (res.status < 200 || res.status >= 300)
37
+ throw new Error(`GitHub answered ${res.status} to GET ${path}`);
38
+ return (text === '' ? null : JSON.parse(text));
39
+ }
40
+ const str = (x) => (typeof x === 'string' && x !== '' ? x : null);
41
+ return {
42
+ async signals(sha) {
43
+ if (!/^[0-9a-f]{7,64}$/.test(sha))
44
+ return [];
45
+ const out = [];
46
+ const seen = new Set();
47
+ const status = await get(`/commits/${sha}/status`);
48
+ for (const s of status?.statuses ?? []) {
49
+ const name = String(s['context'] ?? '');
50
+ if (name === '' || seen.has(`s:${name}`))
51
+ continue; // newest first: the first of each context is its state
52
+ seen.add(`s:${name}`);
53
+ const st = String(s['state']);
54
+ out.push({
55
+ source: 'status',
56
+ name,
57
+ state: st === 'success' ? 'live' : st === 'pending' ? 'building' : 'failed',
58
+ url: str(s['target_url']),
59
+ description: str(s['description']),
60
+ });
61
+ }
62
+ const deployments = await get(`/deployments?sha=${sha}&per_page=20`);
63
+ for (const d of deployments ?? []) {
64
+ const name = String(d['environment'] ?? '');
65
+ if (name === '' || seen.has(`d:${name}`))
66
+ continue;
67
+ seen.add(`d:${name}`);
68
+ const statuses = await get(`/deployments/${Number(d['id'])}/statuses?per_page=1`);
69
+ const latest = (statuses ?? [])[0];
70
+ const st = String(latest?.['state'] ?? 'pending');
71
+ out.push({
72
+ source: 'deployment',
73
+ name,
74
+ state: st === 'success' ? 'live' : st === 'inactive' ? 'inactive' : st === 'failure' || st === 'error' ? 'failed' : 'building',
75
+ url: str(latest?.['environment_url']) ?? str(latest?.['target_url']),
76
+ description: str(latest?.['description']),
77
+ });
78
+ }
79
+ return out;
80
+ },
81
+ async sourceTarballUrl(sha) {
82
+ if (!/^[0-9a-f]{7,64}$/.test(sha))
83
+ return null;
84
+ const res = await doFetch(`${base}/tarball/${sha}`, { method: 'GET', headers, redirect: 'manual' });
85
+ const loc = res.headers?.get('location') ?? null;
86
+ return res.status >= 300 && res.status < 400 && loc !== null && loc.startsWith('https://') ? loc : null;
87
+ },
88
+ async openPulls(limit) {
89
+ const list = await get(`/pulls?state=open&per_page=${Math.max(1, Math.min(limit, 30))}&sort=updated&direction=desc`);
90
+ return (list ?? []).map((p) => ({
91
+ number: Number(p['number']),
92
+ url: String(p['html_url'] ?? ''),
93
+ branch: String(p['head']?.['ref'] ?? ''),
94
+ headSha: String(p['head']?.['sha'] ?? ''),
95
+ title: String(p['title'] ?? ''),
96
+ }));
97
+ },
98
+ };
99
+ }
100
+ /* ───────────────────────────────────────────────────── which signal is it ── */
101
+ /** Netlify's deploy-preview signal for this pull request, if any; `site` narrows it to one site. */
102
+ function netlifySignal(signals, site) {
103
+ const mine = (s) => {
104
+ const ctx = s.name.toLowerCase();
105
+ const url = (s.url ?? '').toLowerCase();
106
+ const isNetlify = (s.source === 'status' && /^netlify\/[^/]+\/deploy-preview$/.test(ctx)) ||
107
+ (s.source === 'deployment' && /deploy[- ]preview|netlify/.test(ctx)) ||
108
+ /\.netlify\.app(\/|$)/.test(url);
109
+ if (!isNetlify)
110
+ return false;
111
+ if (site === undefined)
112
+ return true;
113
+ return ctx === `netlify/${site.toLowerCase()}/deploy-preview` || url.includes(`--${site.toLowerCase()}.netlify.app`);
114
+ };
115
+ // A commit status names the site explicitly; prefer it over a deployment.
116
+ return signals.find((s) => s.source === 'status' && mine(s)) ?? signals.find(mine) ?? null;
117
+ }
118
+ /** Heroku's review-app deployment for pull request `n` of `pipeline`, if GitHub has one. */
119
+ function herokuSignal(signals, pipeline, n) {
120
+ const p = pipeline.toLowerCase();
121
+ const pr = new RegExp(`-pr-${n}(?![0-9])`);
122
+ return (signals.find((s) => {
123
+ if (s.source !== 'deployment')
124
+ return false;
125
+ const env = s.name.toLowerCase();
126
+ const url = (s.url ?? '').toLowerCase();
127
+ return (pr.test(env) || pr.test(url)) && (env.includes(p) || url.includes('herokuapp.com'));
128
+ }) ?? null);
129
+ }
130
+ //# sourceMappingURL=preview-github.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-github.js","sourceRoot":"","sources":["../src/preview-github.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type PreviewSettings } from '@looply/core';
2
+ /**
3
+ * THE PROJECT'S REMOTE PREVIEW CHOICE, AS THIS MACHINE LAST HEARD IT.
4
+ *
5
+ * Set in AutoRyn per component; pulled by the daemon with the delivery setting
6
+ * (every five minutes) and written here. A failed pull keeps the last answer.
7
+ *
8
+ * Precedence: config.yaml `preview.provider` → this cache → none.
9
+ */
10
+ export declare const PREVIEW_SETTINGS_FILE = "preview-settings.json";
11
+ export interface PreviewSettingsCache {
12
+ settings: PreviewSettings | null;
13
+ updatedAt: string | null;
14
+ fetchedAt: string;
15
+ }
16
+ export declare function readPreviewSettingsCache(stateDir: string): PreviewSettingsCache | null;
17
+ /** The provider in force: config.yaml's, else the remote one, else null (none). */
18
+ export declare function resolvePreview(local: PreviewSettings | undefined, stateDir: string): {
19
+ settings: PreviewSettings | null;
20
+ from: 'config.yaml' | 'remote' | 'default';
21
+ };
22
+ /**
23
+ * One pull: ask the server, write the cache when the answer changed. Never
24
+ * throws. A server without the route (older looply-api) answers 404, which is
25
+ * "no preview", quietly.
26
+ */
27
+ export declare function makePreviewSettingsPull(deps: {
28
+ fetch(): Promise<unknown>;
29
+ stateDir: string;
30
+ log(line: string): void;
31
+ now(): number;
32
+ }): () => Promise<'updated' | 'unchanged' | 'failed'>;
33
+ //# sourceMappingURL=preview-settings-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-settings-cache.d.ts","sourceRoot":"","sources":["../src/preview-settings-cache.ts"],"names":[],"mappings":"AAEA,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAG7E;;;;;;;GAOG;AAEH,eAAO,MAAM,qBAAqB,0BAA0B,CAAC;AAE7D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAiBtF;AAED,mFAAmF;AACnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,eAAe,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG;IAAE,QAAQ,EAAE,eAAe,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,GAAG,QAAQ,GAAG,SAAS,CAAA;CAAE,CAKrK;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAC5C,KAAK,IAAI,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,IAAI,MAAM,CAAC;CACf,GAAG,MAAM,OAAO,CAAC,SAAS,GAAG,WAAW,GAAG,QAAQ,CAAC,CA+CpD"}
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PREVIEW_SETTINGS_FILE = void 0;
4
+ exports.readPreviewSettingsCache = readPreviewSettingsCache;
5
+ exports.resolvePreview = resolvePreview;
6
+ exports.makePreviewSettingsPull = makePreviewSettingsPull;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = require("node:path");
9
+ const core_1 = require("@looply/core");
10
+ const server_client_1 = require("./server-client");
11
+ /**
12
+ * THE PROJECT'S REMOTE PREVIEW CHOICE, AS THIS MACHINE LAST HEARD IT.
13
+ *
14
+ * Set in AutoRyn per component; pulled by the daemon with the delivery setting
15
+ * (every five minutes) and written here. A failed pull keeps the last answer.
16
+ *
17
+ * Precedence: config.yaml `preview.provider` → this cache → none.
18
+ */
19
+ exports.PREVIEW_SETTINGS_FILE = 'preview-settings.json';
20
+ function readPreviewSettingsCache(stateDir) {
21
+ try {
22
+ const raw = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(stateDir, exports.PREVIEW_SETTINGS_FILE), 'utf8'));
23
+ let settings = null;
24
+ if (raw['settings'] !== null && raw['settings'] !== undefined) {
25
+ const v = (0, core_1.validatePreviewSettings)(raw['settings']);
26
+ if (!v.ok)
27
+ return null;
28
+ settings = v.settings;
29
+ }
30
+ return {
31
+ settings,
32
+ updatedAt: typeof raw['updatedAt'] === 'string' ? raw['updatedAt'] : null,
33
+ fetchedAt: typeof raw['fetchedAt'] === 'string' ? raw['fetchedAt'] : '',
34
+ };
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
40
+ /** The provider in force: config.yaml's, else the remote one, else null (none). */
41
+ function resolvePreview(local, stateDir) {
42
+ if (local !== undefined)
43
+ return { settings: local.provider === 'none' ? null : local, from: 'config.yaml' };
44
+ const remote = readPreviewSettingsCache(stateDir)?.settings ?? null;
45
+ if (remote !== null && remote.provider !== 'none')
46
+ return { settings: remote, from: 'remote' };
47
+ return { settings: null, from: 'default' };
48
+ }
49
+ /**
50
+ * One pull: ask the server, write the cache when the answer changed. Never
51
+ * throws. A server without the route (older looply-api) answers 404, which is
52
+ * "no preview", quietly.
53
+ */
54
+ function makePreviewSettingsPull(deps) {
55
+ let failureReported = false;
56
+ const reportOnce = (why) => {
57
+ if (failureReported)
58
+ return;
59
+ failureReported = true;
60
+ deps.log(`preview : could not refresh the project's preview setting — ${why}`);
61
+ };
62
+ return async () => {
63
+ let body;
64
+ try {
65
+ body = await deps.fetch();
66
+ }
67
+ catch (e) {
68
+ if (e instanceof server_client_1.ServerError && e.status === 404)
69
+ return 'failed';
70
+ if (!(e instanceof server_client_1.ServerError && (e.isOffline || e.isAuth)))
71
+ reportOnce(e.message);
72
+ return 'failed';
73
+ }
74
+ const b = body;
75
+ if (typeof b !== 'object' || b === null || !('settings' in b)) {
76
+ reportOnce('the server answered with something that is not a settings envelope');
77
+ return 'failed';
78
+ }
79
+ let settings = null;
80
+ if (b.settings !== null) {
81
+ const v = (0, core_1.validatePreviewSettings)(b.settings);
82
+ if (!v.ok) {
83
+ reportOnce(`the server sent a setting this build refuses (${v.key}: ${v.because})`);
84
+ return 'failed';
85
+ }
86
+ settings = v.settings;
87
+ }
88
+ failureReported = false;
89
+ const updatedAt = typeof b.updatedAt === 'string' ? b.updatedAt : null;
90
+ const before = readPreviewSettingsCache(deps.stateDir);
91
+ if (before !== null && JSON.stringify(before.settings) === JSON.stringify(settings) && before.updatedAt === updatedAt)
92
+ return 'unchanged';
93
+ try {
94
+ (0, node_fs_1.mkdirSync)(deps.stateDir, { recursive: true });
95
+ const target = (0, node_path_1.join)(deps.stateDir, exports.PREVIEW_SETTINGS_FILE);
96
+ const tmp = `${target}.${process.pid}.tmp`;
97
+ (0, node_fs_1.writeFileSync)(tmp, JSON.stringify({ settings, updatedAt, fetchedAt: new Date(deps.now()).toISOString() }, null, 2) + '\n', 'utf8');
98
+ (0, node_fs_1.renameSync)(tmp, target);
99
+ }
100
+ catch (e) {
101
+ deps.log(`preview : could not write the preview setting cache (${e.message})`);
102
+ return 'failed';
103
+ }
104
+ deps.log(`preview : project preview setting is ${settings === null ? 'none' : settings.provider}`);
105
+ return 'updated';
106
+ };
107
+ }
108
+ //# sourceMappingURL=preview-settings-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-settings-cache.js","sourceRoot":"","sources":["../src/preview-settings-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ import type { LooplyConfig } from './config';
2
+ import { type GitHubApi } from './github-api';
3
+ import { type HerokuApi } from './heroku-api';
4
+ import { type PreviewCommandRunner } from './preview-environments';
5
+ import { type GitHubPreviewReader } from './preview-github';
6
+ import type { ServerClient } from './server-client';
7
+ /**
8
+ * THE DAEMON'S HANDLE ON PREVIEW SITES: built once per project, so `daemon-cmd`
9
+ * gains one call on its five-minute cadence and nothing else.
10
+ *
11
+ * tick() — pull the project's preview setting, then one pass over delivered
12
+ * pull requests: read or start each open one's preview, tear down
13
+ * each closed one's.
14
+ */
15
+ export interface PreviewAdvancer {
16
+ tick(): Promise<void>;
17
+ }
18
+ export interface PreviewWiringOpts {
19
+ cwd: string;
20
+ stateDir: string;
21
+ config: Pick<LooplyConfig, 'preview'>;
22
+ client: Pick<ServerClient, 'recordPreview' | 'projectPreviewSettings'>;
23
+ projectId: string;
24
+ log(line: string): void;
25
+ /** Tests: fakes for every outside party. */
26
+ github?: (owner: string, repo: string) => Promise<{
27
+ api: GitHubApi;
28
+ reader: GitHubPreviewReader;
29
+ } | null>;
30
+ heroku?: () => HerokuApi | null;
31
+ runCommand?: PreviewCommandRunner;
32
+ now?: () => number;
33
+ }
34
+ export declare function makePreviewAdvancer(opts: PreviewWiringOpts): PreviewAdvancer;
35
+ //# sourceMappingURL=preview-wiring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-wiring.d.ts","sourceRoot":"","sources":["../src/preview-wiring.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EAA8B,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAC1E,OAAO,EASL,KAAK,oBAAoB,EAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAA2B,KAAK,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAErF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;IACtC,MAAM,EAAE,IAAI,CAAC,YAAY,EAAE,eAAe,GAAG,wBAAwB,CAAC,CAAC;IACvE,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,GAAG,EAAE,SAAS,CAAC;QAAC,MAAM,EAAE,mBAAmB,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;IAC1G,MAAM,CAAC,EAAE,MAAM,SAAS,GAAG,IAAI,CAAC;IAChC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,eAAe,CAkE5E"}
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makePreviewAdvancer = makePreviewAdvancer;
4
+ const branch_delivery_1 = require("./branch-delivery");
5
+ const github_api_1 = require("./github-api");
6
+ const heroku_api_1 = require("./heroku-api");
7
+ const preview_environments_1 = require("./preview-environments");
8
+ const preview_github_1 = require("./preview-github");
9
+ const preview_settings_cache_1 = require("./preview-settings-cache");
10
+ function makePreviewAdvancer(opts) {
11
+ const now = opts.now ?? (() => Date.now());
12
+ const pull = (0, preview_settings_cache_1.makePreviewSettingsPull)({
13
+ fetch: () => opts.client.projectPreviewSettings(opts.projectId),
14
+ stateDir: opts.stateDir,
15
+ log: opts.log,
16
+ now,
17
+ });
18
+ const github = opts.github ??
19
+ (async (owner, repo) => {
20
+ const t = await (0, github_api_1.githubToken)();
21
+ if (t === null)
22
+ return null;
23
+ return { api: (0, github_api_1.makeGitHubApi)({ owner, repo, token: t.token }), reader: (0, preview_github_1.makeGitHubPreviewReader)({ owner, repo, token: t.token }) };
24
+ });
25
+ const heroku = opts.heroku ??
26
+ (() => {
27
+ const t = (0, heroku_api_1.herokuToken)();
28
+ return t === null ? null : (0, heroku_api_1.makeHerokuApi)({ token: t.token });
29
+ });
30
+ const run = opts.runCommand ?? (0, preview_environments_1.makePreviewCommandRunner)(opts.cwd, opts.stateDir);
31
+ const settings = () => (0, preview_settings_cache_1.resolvePreview)(opts.config.preview?.settings, opts.stateDir).settings;
32
+ /** One command driver for the life of the daemon: it holds the background builds. */
33
+ const command = (0, preview_environments_1.commandDriver)({ ...(opts.config.preview?.command ? { command: opts.config.preview.command } : {}), ...(opts.config.preview?.teardown ? { teardown: opts.config.preview.teardown } : {}) }, run);
34
+ const store = (0, preview_environments_1.filePreviewStore)(opts.stateDir);
35
+ const deliveries = (0, branch_delivery_1.fileDeliveryStore)(opts.stateDir);
36
+ return {
37
+ async tick() {
38
+ await pull();
39
+ // A reader per repository per pass; the drivers read from it.
40
+ const clients = new Map();
41
+ const clientFor = async (owner, repo) => {
42
+ const k = `${owner}/${repo}`;
43
+ if (!clients.has(k))
44
+ clients.set(k, await github(owner, repo).catch(() => null));
45
+ return clients.get(k);
46
+ };
47
+ let current = null;
48
+ const herokuApi = heroku();
49
+ const driver = (provider) => {
50
+ if (current === null)
51
+ return null;
52
+ const s = settings();
53
+ if (provider === 'command')
54
+ return command;
55
+ if (provider === 'netlify')
56
+ return (0, preview_environments_1.netlifyDriver)(s?.provider === 'netlify' ? s : {}, current.reader);
57
+ // An existing heroku preview whose setting moved away is still torn down on its pipeline name when known.
58
+ if (s?.provider === 'heroku')
59
+ return (0, preview_environments_1.herokuDriver)(s, current.reader, herokuApi);
60
+ return null;
61
+ };
62
+ await (0, preview_environments_1.advancePreviews)({
63
+ settings,
64
+ driver,
65
+ deliveries,
66
+ store,
67
+ github: async (owner, repo) => {
68
+ current = await clientFor(owner, repo);
69
+ return current?.api ?? null;
70
+ },
71
+ record: async (goalId, report) => {
72
+ if (goalId.startsWith('gol_'))
73
+ await opts.client.recordPreview(opts.projectId, goalId, report);
74
+ },
75
+ now,
76
+ log: opts.log,
77
+ });
78
+ },
79
+ };
80
+ }
81
+ //# sourceMappingURL=preview-wiring.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-wiring.js","sourceRoot":"","sources":["../src/preview-wiring.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import type { SpendRecord } from './spend-ledger';
2
+ /**
3
+ * WHAT EACH RUN COST, SENT TO THE SERVER SO A DASHBOARD CAN SAY "COST PER TICKET".
4
+ *
5
+ * The spend ledger (`~/.looply/spend.jsonl`) is machine-local and append-only;
6
+ * nothing else ever left the machine. This totals its rows per run and hands the
7
+ * server the totals — the server already knows which goal each run served, so it
8
+ * can answer "what did this ticket cost" without the machine knowing about tickets.
9
+ *
10
+ * ⚠️ ONLY `spend` ROWS ARE SUMMED. A reservation is an estimate, and a settlement
11
+ * restates rows that are already here; adding either would count money twice.
12
+ *
13
+ * ⚠️ AN INVOCATION WITH NO DOLLARS IS COUNTED AS UNMEASURED, NEVER AS FREE. The
14
+ * dashboard shows the measured dollars and says how many calls it could not price.
15
+ *
16
+ * Read-only on the ledger, bounded per call, and a failure only waits for the
17
+ * next cadence.
18
+ */
19
+ export interface RunSpendSummary {
20
+ readonly runId: string;
21
+ /** Model invocations recorded for the run. */
22
+ readonly invocations: number;
23
+ /** Of those, how many reported no dollar figure. */
24
+ readonly unmeasured: number;
25
+ /** Dollars of the invocations that reported one. */
26
+ readonly usd: number;
27
+ readonly inputTokens: number;
28
+ readonly outputTokens: number;
29
+ readonly cacheReadTokens: number;
30
+ readonly cacheWriteTokens: number;
31
+ /** Wall clock spent inside model invocations. */
32
+ readonly ms: number;
33
+ /** ISO time of the run's latest row. */
34
+ readonly lastAt: string;
35
+ }
36
+ /** Runs older than this are not reported (the dashboard looks back 90 days). */
37
+ export declare const RUN_SPEND_LOOKBACK_MS: number;
38
+ /** At most this many runs per request; the rest go on the next cadence. */
39
+ export declare const RUN_SPEND_BATCH = 200;
40
+ /** Totals per run, newest run first, for runs whose latest row is after `sinceMs`. */
41
+ export declare function summariseRunSpend(records: readonly SpendRecord[], sinceMs: number): RunSpendSummary[];
42
+ export interface RunSpendReporterDeps {
43
+ /** The ledger's rows; `null` when it cannot be read (nothing is sent). */
44
+ readonly read: () => readonly SpendRecord[] | null;
45
+ /** Sends one batch; resolves when the server took it. */
46
+ readonly send: (runs: readonly RunSpendSummary[]) => Promise<unknown>;
47
+ readonly now?: () => number;
48
+ readonly log?: (line: string) => void;
49
+ }
50
+ /**
51
+ * One reporting pass per call. Sends only runs whose totals changed since the
52
+ * last pass that the server accepted, so a quiet machine sends nothing.
53
+ */
54
+ export declare function makeRunSpendReporter(deps: RunSpendReporterDeps): () => Promise<number>;
55
+ //# sourceMappingURL=run-spend-report.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spend-report.d.ts","sourceRoot":"","sources":["../src/run-spend-report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD;;;;;;;;;;;;;;;;GAgBG;AAEH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,oDAAoD;IACpD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,iDAAiD;IACjD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,QAA2B,CAAC;AAC9D,2EAA2E;AAC3E,eAAO,MAAM,eAAe,MAAM,CAAC;AAInC,sFAAsF;AACtF,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAmCrG;AAED,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,IAAI,EAAE,MAAM,SAAS,WAAW,EAAE,GAAG,IAAI,CAAC;IACnD,yDAAyD;IACzD,QAAQ,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,SAAS,eAAe,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IAC5B,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACvC;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CAuBtF"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RUN_SPEND_BATCH = exports.RUN_SPEND_LOOKBACK_MS = void 0;
4
+ exports.summariseRunSpend = summariseRunSpend;
5
+ exports.makeRunSpendReporter = makeRunSpendReporter;
6
+ /** Runs older than this are not reported (the dashboard looks back 90 days). */
7
+ exports.RUN_SPEND_LOOKBACK_MS = 90 * 24 * 60 * 60 * 1000;
8
+ /** At most this many runs per request; the rest go on the next cadence. */
9
+ exports.RUN_SPEND_BATCH = 200;
10
+ const n = (v) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : 0);
11
+ /** Totals per run, newest run first, for runs whose latest row is after `sinceMs`. */
12
+ function summariseRunSpend(records, sinceMs) {
13
+ const byRun = new Map();
14
+ for (const r of records) {
15
+ if (typeof r.runId !== 'string' || r.runId === '')
16
+ continue;
17
+ if (r.kind !== undefined && r.kind !== 'spend')
18
+ continue;
19
+ let s = byRun.get(r.runId);
20
+ if (!s) {
21
+ s = {
22
+ runId: r.runId,
23
+ invocations: 0,
24
+ unmeasured: 0,
25
+ usd: 0,
26
+ inputTokens: 0,
27
+ outputTokens: 0,
28
+ cacheReadTokens: 0,
29
+ cacheWriteTokens: 0,
30
+ ms: 0,
31
+ lastAt: r.at,
32
+ };
33
+ byRun.set(r.runId, s);
34
+ }
35
+ s.invocations += 1;
36
+ if (typeof r.usd === 'number' && Number.isFinite(r.usd))
37
+ s.usd += r.usd;
38
+ else
39
+ s.unmeasured += 1;
40
+ s.inputTokens += n(r.inputTokens);
41
+ s.outputTokens += n(r.outputTokens);
42
+ s.cacheReadTokens += n(r.cacheReadTokens);
43
+ s.cacheWriteTokens += n(r.cacheWriteTokens);
44
+ s.ms += n(r.ms);
45
+ if (Date.parse(r.at) > Date.parse(s.lastAt))
46
+ s.lastAt = r.at;
47
+ }
48
+ return [...byRun.values()]
49
+ .filter((s) => Date.parse(s.lastAt) >= sinceMs)
50
+ .map((s) => ({ ...s, usd: Math.round(s.usd * 1e6) / 1e6 }))
51
+ .sort((a, b) => Date.parse(b.lastAt) - Date.parse(a.lastAt));
52
+ }
53
+ /**
54
+ * One reporting pass per call. Sends only runs whose totals changed since the
55
+ * last pass that the server accepted, so a quiet machine sends nothing.
56
+ */
57
+ function makeRunSpendReporter(deps) {
58
+ const sent = new Map();
59
+ const now = deps.now ?? (() => Date.now());
60
+ let failing = false;
61
+ return async () => {
62
+ const records = deps.read();
63
+ if (records === null)
64
+ return 0;
65
+ const changed = summariseRunSpend(records, now() - exports.RUN_SPEND_LOOKBACK_MS).filter((s) => sent.get(s.runId) !== `${s.invocations}|${s.lastAt}`);
66
+ const batch = changed.slice(0, exports.RUN_SPEND_BATCH);
67
+ if (batch.length === 0)
68
+ return 0;
69
+ try {
70
+ await deps.send(batch);
71
+ }
72
+ catch (e) {
73
+ if (!failing)
74
+ deps.log?.(`spend : could not report run costs (${e.message}); will retry`);
75
+ failing = true;
76
+ return 0;
77
+ }
78
+ failing = false;
79
+ for (const s of batch)
80
+ sent.set(s.runId, `${s.invocations}|${s.lastAt}`);
81
+ return batch.length;
82
+ };
83
+ }
84
+ //# sourceMappingURL=run-spend-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run-spend-report.js","sourceRoot":"","sources":["../src/run-spend-report.ts"],"names":[],"mappings":";;;AA8CA,8CAmCC;AAeD,oDAuBC;AAjFD,gFAAgF;AACnE,QAAA,qBAAqB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAC9D,2EAA2E;AAC9D,QAAA,eAAe,GAAG,GAAG,CAAC;AAEnC,MAAM,CAAC,GAAG,CAAC,CAAU,EAAU,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAElG,sFAAsF;AACtF,SAAgB,iBAAiB,CAAC,OAA+B,EAAE,OAAe;IAChF,MAAM,KAAK,GAAG,IAAI,GAAG,EAA0E,CAAC;IAChG,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE;YAAE,SAAS;QAC5D,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,OAAO;YAAE,SAAS;QACzD,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG;gBACF,KAAK,EAAE,CAAC,CAAC,KAAK;gBACd,WAAW,EAAE,CAAC;gBACd,UAAU,EAAE,CAAC;gBACb,GAAG,EAAE,CAAC;gBACN,WAAW,EAAE,CAAC;gBACd,YAAY,EAAE,CAAC;gBACf,eAAe,EAAE,CAAC;gBAClB,gBAAgB,EAAE,CAAC;gBACnB,EAAE,EAAE,CAAC;gBACL,MAAM,EAAE,CAAC,CAAC,EAAE;aACb,CAAC;YACF,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QACxB,CAAC;QACD,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC;QACnB,IAAI,OAAO,CAAC,CAAC,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC;YAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC;;YACnE,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC;QACvB,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC;QAClC,CAAC,CAAC,YAAY,IAAI,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;QACpC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;QAC1C,CAAC,CAAC,gBAAgB,IAAI,CAAC,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;QAC5C,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAChB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;YAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,CAAC;IAC/D,CAAC;IACD,OAAO,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC;SACvB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,OAAO,CAAC;SAC9C,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,GAAG,GAAG,EAAE,CAAC,CAAC;SAC1D,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;AACjE,CAAC;AAWD;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,IAA0B;IAC7D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,OAAO,KAAK,IAAI;YAAE,OAAO,CAAC,CAAC;QAC/B,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,6BAAqB,CAAC,CAAC,MAAM,CAC9E,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,EAAE,CAC5D,CAAC;QACF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAe,CAAC,CAAC;QAChD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,CAAC,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,OAAO;gBAAE,IAAI,CAAC,GAAG,EAAE,CAAC,yCAA0C,CAAW,CAAC,OAAO,eAAe,CAAC,CAAC;YACvG,OAAO,GAAG,IAAI,CAAC;YACf,OAAO,CAAC,CAAC;QACX,CAAC;QACD,OAAO,GAAG,KAAK,CAAC;QAChB,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,OAAO,KAAK,CAAC,MAAM,CAAC;IACtB,CAAC,CAAC;AACJ,CAAC"}
@@ -3,6 +3,8 @@ import type { Attestation, Classification, CoverageKind, Disposition, Requiremen
3
3
  import type { VisualProofUpload } from '@looply/core';
4
4
  import type { AgentSettings, MachineSettings, WorkspacesReport, AccountReport, MachineSlots, BranchDeliveryReport, DeliverySettings, AnyEvent, Goal, GoalAutonomyState, GoalState, MachineSettlableState, PursuitEndCode, GoalHold } from '@looply/core';
5
5
  import type { IngestAck } from './uploader';
6
+ import type { PreviewReport, PreviewSettings } from '@looply/core';
7
+ import type { RunSpendSummary } from './run-spend-report';
6
8
  /**
7
9
  * The machine's client for the looply server.
8
10
  *
@@ -113,6 +115,14 @@ export declare class ServerClient {
113
115
  * (`GET /v1/machine/settings`; the id comes from the token). Cached by the
114
116
  * daemon — see `machine-settings.ts`.
115
117
  */
118
+ /**
119
+ * What each of this project's runs cost, totalled from the machine's spend
120
+ * ledger (`run-spend-report.ts`). The server keeps the totals on the run rows it
121
+ * already has, and ignores run ids that are not this project's on this machine.
122
+ */
123
+ reportRunSpend(machineId: string, projectId: string, runs: readonly RunSpendSummary[]): Promise<{
124
+ updated: number;
125
+ }>;
116
126
  machineSettings(): Promise<{
117
127
  settings: MachineSettings;
118
128
  updatedAt: string | null;
@@ -268,6 +278,18 @@ export declare class ServerClient {
268
278
  }>;
269
279
  at: string;
270
280
  }>;
281
+ /**
282
+ * The project's remote preview choice (`GET /v1/projects/:id/preview-settings`).
283
+ * The daemon caches the answer — see `preview-settings-cache.ts`.
284
+ */
285
+ projectPreviewSettings(projectId: string): Promise<{
286
+ settings: PreviewSettings | null;
287
+ updatedAt: string | null;
288
+ }>;
289
+ /** Where a delivered goal's preview site stands — building, live, failed, removed (machine token). */
290
+ recordPreview(projectId: string, goalId: string, report: PreviewReport): Promise<{
291
+ preview: PreviewReport;
292
+ }>;
271
293
  projectGoals(projectId: string): Promise<{
272
294
  goals: Goal[];
273
295
  }>;