anhedral 0.8.6 → 0.9.0
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/README.md +141 -38
- package/assets/anhedral-cli-init-technical.svg +15 -15
- package/assets/anhedral-cli-init.svg +16 -16
- package/dist/access.d.ts +10 -0
- package/dist/access.js +93 -0
- package/dist/auth-schema-snapshot.d.ts +346 -0
- package/dist/auth-schema-snapshot.js +347 -0
- package/dist/auth-schema.d.ts +1 -0
- package/dist/auth-schema.js +57 -0
- package/dist/bin.js +345 -39
- package/dist/bounded-file.d.ts +2 -0
- package/dist/bounded-file.js +30 -0
- package/dist/capabilities.d.ts +24 -42
- package/dist/capabilities.js +73 -75
- package/dist/cloudflare.d.ts +14 -0
- package/dist/cloudflare.js +66 -0
- package/dist/command.js +12 -3
- package/dist/delivery.d.ts +118 -0
- package/dist/delivery.js +611 -0
- package/dist/dependencies.d.ts +24 -3
- package/dist/dependencies.js +112 -74
- package/dist/dependency-order.d.ts +2 -0
- package/dist/dependency-order.js +24 -0
- package/dist/index.d.ts +18 -3
- package/dist/index.js +12 -2
- package/dist/managed-hosting.d.ts +49 -0
- package/dist/managed-hosting.js +373 -0
- package/dist/management.d.ts +3 -0
- package/dist/management.js +47 -0
- package/dist/platforms/desktop.d.ts +2 -2
- package/dist/platforms/desktop.js +25 -15
- package/dist/platforms/extension.d.ts +2 -2
- package/dist/platforms/extension.js +104 -66
- package/dist/platforms/mobile.d.ts +2 -2
- package/dist/platforms/mobile.js +13 -7
- package/dist/pnpm-policy.d.ts +0 -2
- package/dist/pnpm-policy.js +18 -13
- package/dist/project-state.d.ts +28 -0
- package/dist/project-state.js +226 -0
- package/dist/provider-readiness.d.ts +30 -0
- package/dist/provider-readiness.js +87 -0
- package/dist/provision.d.ts +46 -0
- package/dist/provision.js +197 -0
- package/dist/readiness.d.ts +4 -2
- package/dist/readiness.js +81 -14
- package/dist/registry/add.d.ts +22 -0
- package/dist/registry/add.js +198 -0
- package/dist/registry/bootstrap.d.ts +2 -0
- package/dist/registry/bootstrap.js +14 -0
- package/dist/registry/commands.d.ts +2 -0
- package/dist/registry/commands.js +99 -0
- package/dist/registry/context.d.ts +3 -0
- package/dist/registry/context.js +21 -0
- package/dist/registry/options.d.ts +2 -0
- package/dist/registry/options.js +65 -0
- package/dist/registry/receipt.d.ts +3 -0
- package/dist/registry/receipt.js +73 -0
- package/dist/registry/remote.d.ts +25 -0
- package/dist/registry/remote.js +168 -0
- package/dist/registry/resolve.d.ts +10 -0
- package/dist/registry/resolve.js +98 -0
- package/dist/registry/types.d.ts +64 -0
- package/dist/registry/usage.d.ts +1 -0
- package/dist/registry/usage.js +38 -0
- package/dist/render.d.ts +0 -4
- package/dist/render.js +3 -16
- package/dist/repository.d.ts +24 -0
- package/dist/repository.js +96 -0
- package/dist/scaffold/api.d.ts +2 -0
- package/dist/scaffold/api.js +13 -0
- package/dist/scaffold/auth.d.ts +3 -0
- package/dist/scaffold/auth.js +95 -0
- package/dist/scaffold/bootstrap.d.ts +2 -0
- package/dist/scaffold/bootstrap.js +34 -0
- package/dist/scaffold/compose.d.ts +4 -0
- package/dist/scaffold/compose.js +34 -0
- package/dist/scaffold/database.d.ts +2 -0
- package/dist/scaffold/database.js +43 -0
- package/dist/scaffold/documentation.d.ts +3 -0
- package/dist/scaffold/documentation.js +91 -0
- package/dist/scaffold/environment.d.ts +2 -0
- package/dist/scaffold/environment.js +5 -0
- package/dist/scaffold/files.d.ts +25 -0
- package/dist/scaffold/files.js +108 -0
- package/dist/scaffold/integrations.d.ts +3 -0
- package/dist/scaffold/integrations.js +7 -0
- package/dist/scaffold/services.d.ts +5 -0
- package/dist/scaffold/services.js +84 -0
- package/dist/scaffold/web.d.ts +2 -0
- package/dist/scaffold/web.js +102 -0
- package/dist/scaffold/workspace.d.ts +3 -0
- package/dist/scaffold/workspace.js +150 -0
- package/dist/security-backports.js +7 -2
- package/dist/stack-registry.d.ts +80 -0
- package/dist/stack-registry.js +424 -0
- package/dist/standard.d.ts +6 -19
- package/dist/standard.js +46 -653
- package/dist/transaction.js +47 -56
- package/dist/util.js +12 -2
- package/dist/vercel.d.ts +98 -0
- package/dist/vercel.js +796 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/vinext.d.ts +1 -0
- package/dist/vinext.js +83 -0
- package/dist/workspace-graph.d.ts +7 -0
- package/dist/workspace-graph.js +73 -0
- package/docs/application-stack-standard.md +76 -91
- package/docs/delivery-workflow.md +83 -0
- package/docs/managed-hosting-architecture.md +87 -0
- package/docs/sponsorships.md +25 -0
- package/package.json +23 -29
- package/dist/evidence-validation.d.ts +0 -2
- package/dist/evidence-validation.js +0 -18
- package/dist/foundation.d.ts +0 -6
- package/dist/foundation.js +0 -49
- package/dist/progress-view.d.ts +0 -57
- package/dist/progress-view.js +0 -69
- package/dist/progress.d.ts +0 -95
- package/dist/progress.js +0 -327
- package/dist/project.d.ts +0 -5
- package/dist/standard-products.d.ts +0 -2
- package/dist/standard-products.js +0 -6
- /package/assets/{images/svg/logo-white-subtract.svg → anhedral-mark.svg} +0 -0
- /package/dist/{project.js → registry/types.js} +0 -0
package/dist/progress.js
DELETED
|
@@ -1,327 +0,0 @@
|
|
|
1
|
-
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { closeSync, constants, existsSync, fsyncSync, fstatSync, lstatSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { hostname } from 'node:os';
|
|
5
|
-
import { isNonSecretEvidence, isProgressEnvironment } from './evidence-validation.js';
|
|
6
|
-
export const PROGRESS_FILE = 'anhedral.progress.json';
|
|
7
|
-
export const MILESTONES = ['generated', 'provisioned', 'connected', 'tested', 'deployed'];
|
|
8
|
-
const MAX_BYTES = 262144;
|
|
9
|
-
function object(value, keys) {
|
|
10
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
11
|
-
throw new Error('Expected a progress object.');
|
|
12
|
-
const result = value;
|
|
13
|
-
if (Object.keys(result).some(key => !keys.includes(key)))
|
|
14
|
-
throw new Error('Unknown progress field.');
|
|
15
|
-
return result;
|
|
16
|
-
}
|
|
17
|
-
function text(value, max = 600) {
|
|
18
|
-
if (typeof value !== 'string' || !value.trim() || value.length > max || !isNonSecretEvidence(value))
|
|
19
|
-
throw new Error('Progress requires bounded nonsecret text.');
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
function id(value) {
|
|
23
|
-
const result = text(value, 80);
|
|
24
|
-
if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(result) || ['__proto__', 'constructor', 'prototype'].includes(result))
|
|
25
|
-
throw new Error('Invalid progress identifier.');
|
|
26
|
-
return result;
|
|
27
|
-
}
|
|
28
|
-
function environment(value) {
|
|
29
|
-
const result = text(value, 80);
|
|
30
|
-
if (!isProgressEnvironment(result))
|
|
31
|
-
throw new Error('Invalid progress environment.');
|
|
32
|
-
return result;
|
|
33
|
-
}
|
|
34
|
-
function date(value) {
|
|
35
|
-
const result = text(value, 30);
|
|
36
|
-
if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/.test(result) || !Number.isFinite(Date.parse(result)))
|
|
37
|
-
throw new Error('Expected a UTC timestamp.');
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
function member(value, options) {
|
|
41
|
-
if (!options.includes(value))
|
|
42
|
-
throw new Error('Invalid progress category.');
|
|
43
|
-
return value;
|
|
44
|
-
}
|
|
45
|
-
function integer(value) {
|
|
46
|
-
if (!Number.isSafeInteger(value) || value < 0)
|
|
47
|
-
throw new Error('Invalid progress revision.');
|
|
48
|
-
return value;
|
|
49
|
-
}
|
|
50
|
-
function list(value, validate, max) {
|
|
51
|
-
if (!Array.isArray(value) || value.length > max)
|
|
52
|
-
throw new Error('Progress list exceeds its limit.');
|
|
53
|
-
return value.map(validate);
|
|
54
|
-
}
|
|
55
|
-
function url(value) {
|
|
56
|
-
const result = text(value, 2048);
|
|
57
|
-
const parsed = new URL(result);
|
|
58
|
-
if (parsed.username || parsed.password || parsed.search || parsed.hash || !(parsed.protocol === 'https:' || (parsed.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(parsed.hostname))))
|
|
59
|
-
throw new Error('Use a credential-free HTTPS or loopback preview URL without query or fragment.');
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
function observation(value, discovery = false) {
|
|
63
|
-
const shared = ['outcome', 'source', 'observedAt', 'evidence', 'accountRef'];
|
|
64
|
-
const item = object(value, [...shared, ...(discovery ? ['provider', 'route', 'check', 'expiresAt'] : ['capability', 'milestone', 'configurationFingerprint', 'resourceRef', 'revisionRef'])]);
|
|
65
|
-
const evidence = { outcome: member(item.outcome, ['passed', 'failed', 'blocked']), source: member(item.source, ['agent', 'independent']), observedAt: date(item.observedAt), evidence: text(item.evidence) };
|
|
66
|
-
if (item.accountRef !== undefined)
|
|
67
|
-
evidence.accountRef = text(item.accountRef, 160);
|
|
68
|
-
if (discovery) {
|
|
69
|
-
const expiresAt = date(item.expiresAt);
|
|
70
|
-
if (Date.parse(expiresAt) <= Date.parse(evidence.observedAt) || Date.parse(expiresAt) - Date.parse(evidence.observedAt) > 86400000 * 30)
|
|
71
|
-
throw new Error('Discovery expires within 30 days of observation.');
|
|
72
|
-
if (evidence.outcome === 'passed' && ['authorized', 'integration-tested'].includes(String(item.check)) && !evidence.accountRef)
|
|
73
|
-
throw new Error('Successful account authorization and integration checks require an account reference.');
|
|
74
|
-
return { ...evidence, provider: id(item.provider), route: member(item.route, ['plugin', 'api', 'cli', 'browser']), check: member(item.check, ['installed', 'callable', 'local-working', 'authorized', 'integration-tested']), expiresAt };
|
|
75
|
-
}
|
|
76
|
-
const fingerprint = text(item.configurationFingerprint, 64);
|
|
77
|
-
if (!/^[a-f0-9]{64}$/.test(fingerprint))
|
|
78
|
-
throw new Error('Invalid configuration fingerprint.');
|
|
79
|
-
const result = { ...evidence, capability: id(item.capability), milestone: member(item.milestone, MILESTONES), configurationFingerprint: fingerprint };
|
|
80
|
-
for (const key of ['resourceRef', 'revisionRef'])
|
|
81
|
-
if (item[key] !== undefined)
|
|
82
|
-
result[key] = text(item[key], 160);
|
|
83
|
-
return result;
|
|
84
|
-
}
|
|
85
|
-
export function validateProgressReport(value) {
|
|
86
|
-
const item = object(value, ['revision', 'environment', 'observations', 'discovery', 'blockers', 'nextAction', 'previewUrl', 'liveUrl']);
|
|
87
|
-
const result = { revision: integer(item.revision), environment: environment(item.environment) };
|
|
88
|
-
if (item.observations !== undefined)
|
|
89
|
-
result.observations = list(item.observations, v => observation(v), 100);
|
|
90
|
-
if (item.discovery !== undefined)
|
|
91
|
-
result.discovery = list(item.discovery, v => observation(v, true), 100);
|
|
92
|
-
if (item.blockers !== undefined)
|
|
93
|
-
result.blockers = list(item.blockers, v => text(v), 20);
|
|
94
|
-
if (item.nextAction !== undefined)
|
|
95
|
-
result.nextAction = item.nextAction === null ? null : text(item.nextAction);
|
|
96
|
-
for (const key of ['previewUrl', 'liveUrl'])
|
|
97
|
-
if (item[key] !== undefined)
|
|
98
|
-
result[key] = item[key] === null ? null : url(item[key]);
|
|
99
|
-
if (JSON.stringify(result).length > MAX_BYTES)
|
|
100
|
-
throw new Error('Progress report too large.');
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
export function validateProjectProgress(value) {
|
|
104
|
-
const item = object(value, ['schemaVersion', 'revision', 'updatedAt', 'environments']);
|
|
105
|
-
if (item.schemaVersion !== 1)
|
|
106
|
-
throw new Error('Unsupported progress schema.');
|
|
107
|
-
const environments = list(item.environments, entry => {
|
|
108
|
-
const env = object(entry, ['id', 'observations', 'discovery', 'blockers', 'nextAction', 'previewUrl', 'liveUrl']);
|
|
109
|
-
for (const key of ['nextAction', 'previewUrl', 'liveUrl'])
|
|
110
|
-
if (env[key] === null)
|
|
111
|
-
throw new Error('Persisted progress fields must be strings or absent.');
|
|
112
|
-
const { id: environment, ...fields } = env;
|
|
113
|
-
const report = validateProgressReport({ ...fields, environment, revision: 0 });
|
|
114
|
-
return { id: report.environment, observations: report.observations ?? [], discovery: report.discovery ?? [], blockers: report.blockers ?? [], ...Object.fromEntries(['nextAction', 'previewUrl', 'liveUrl'].filter(key => env[key] !== undefined).map(key => [key, env[key]])) };
|
|
115
|
-
}, 20);
|
|
116
|
-
if (new Set(environments.map(env => env.id)).size !== environments.length)
|
|
117
|
-
throw new Error('Duplicate progress environment.');
|
|
118
|
-
return { schemaVersion: 1, revision: integer(item.revision), updatedAt: date(item.updatedAt), environments };
|
|
119
|
-
}
|
|
120
|
-
function filePath(root, file) {
|
|
121
|
-
const canonical = realpathSync(root);
|
|
122
|
-
if (!lstatSync(canonical).isDirectory())
|
|
123
|
-
throw new Error('Project root must be a directory.');
|
|
124
|
-
const target = path.join(canonical, file);
|
|
125
|
-
try {
|
|
126
|
-
const entry = lstatSync(target);
|
|
127
|
-
if (!entry.isFile() || entry.isSymbolicLink())
|
|
128
|
-
throw new Error('Progress paths must be regular files.');
|
|
129
|
-
}
|
|
130
|
-
catch (error) {
|
|
131
|
-
if (error.code !== 'ENOENT')
|
|
132
|
-
throw error;
|
|
133
|
-
}
|
|
134
|
-
return target;
|
|
135
|
-
}
|
|
136
|
-
function safeRead(file, limit = MAX_BYTES) {
|
|
137
|
-
const fd = openSync(file, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
138
|
-
try {
|
|
139
|
-
if (fstatSync(fd).size > limit)
|
|
140
|
-
throw new Error('Progress/configuration file exceeds size limit.');
|
|
141
|
-
const data = readFileSync(fd);
|
|
142
|
-
if (data.length > limit)
|
|
143
|
-
throw new Error('Progress/configuration file exceeds size limit.');
|
|
144
|
-
return data;
|
|
145
|
-
}
|
|
146
|
-
finally {
|
|
147
|
-
closeSync(fd);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
export function readProjectProgress(root) {
|
|
151
|
-
const file = filePath(root, PROGRESS_FILE);
|
|
152
|
-
return existsSync(file) ? validateProjectProgress(JSON.parse(safeRead(file).toString('utf8'))) : null;
|
|
153
|
-
}
|
|
154
|
-
/** Bounded source/configuration digest; secrets, dependencies and generated output are excluded. */
|
|
155
|
-
export function projectFingerprint(root) {
|
|
156
|
-
const canonical = realpathSync(root);
|
|
157
|
-
const files = new Set(['anhedral.setup.json', 'anhedral.standard.json', 'package.json', 'pnpm-workspace.yaml', 'pnpm-lock.yaml', 'wrangler.json', 'wrangler.jsonc', 'wrangler.toml']);
|
|
158
|
-
const ignored = new Set(['node_modules', 'dist', 'build', 'out', 'coverage', '.git', '.next', '.turbo', '.artifacts', '.anhedral', '.cache', '.wrangler', '.output', '.expo', '.venv']);
|
|
159
|
-
const sourceFile = (name) => !['cloudflare-env.d.ts', 'worker-configuration.d.ts'].includes(name) && (/\.(?:[cm]?[jt]sx?|css|sql|prisma)$/.test(name) || /^(?:package|wrangler|tsconfig)(?:\.[\w-]+)?\.jsonc?$/.test(name) || name === 'wrangler.toml');
|
|
160
|
-
let entries = 0;
|
|
161
|
-
const visit = (directory, depth) => {
|
|
162
|
-
if (depth > 12)
|
|
163
|
-
throw new Error('Source fingerprint depth limit exceeded.');
|
|
164
|
-
for (const name of readdirSync(path.join(canonical, directory)).sort()) {
|
|
165
|
-
if (++entries > 4000)
|
|
166
|
-
throw new Error('Source fingerprint entry limit exceeded.');
|
|
167
|
-
if (name.startsWith('.') || ignored.has(name) || /(?:secret|credential|private[-_.]?key)/i.test(name))
|
|
168
|
-
continue;
|
|
169
|
-
const relative = path.join(directory, name), entry = lstatSync(path.join(canonical, relative));
|
|
170
|
-
if (entry.isSymbolicLink())
|
|
171
|
-
throw new Error('Source fingerprint paths cannot be symlinks.');
|
|
172
|
-
if (entry.isDirectory())
|
|
173
|
-
visit(relative, depth + 1);
|
|
174
|
-
else if (entry.isFile() && sourceFile(name))
|
|
175
|
-
files.add(relative);
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
for (const directory of ['apps', 'packages', 'src', 'app', 'pages', 'components', 'lib']) {
|
|
179
|
-
const target = path.join(canonical, directory);
|
|
180
|
-
if (!existsSync(target))
|
|
181
|
-
continue;
|
|
182
|
-
if (lstatSync(target).isSymbolicLink())
|
|
183
|
-
throw new Error('Source fingerprint paths cannot be symlinks.');
|
|
184
|
-
if (lstatSync(target).isDirectory())
|
|
185
|
-
visit(directory, 0);
|
|
186
|
-
}
|
|
187
|
-
for (const name of readdirSync(canonical))
|
|
188
|
-
if (!name.startsWith('.') && !/(?:secret|credential|private[-_.]?key)/i.test(name) && sourceFile(name))
|
|
189
|
-
files.add(name);
|
|
190
|
-
const hash = createHash('sha256');
|
|
191
|
-
let bytes = 0;
|
|
192
|
-
for (const file of [...files].sort()) {
|
|
193
|
-
const target = filePath(root, file);
|
|
194
|
-
const content = existsSync(target) ? safeRead(target, file === 'pnpm-lock.yaml' ? 2097152 : MAX_BYTES) : Buffer.from('<absent>');
|
|
195
|
-
bytes += content.length;
|
|
196
|
-
if (bytes > 8388608)
|
|
197
|
-
throw new Error('Source fingerprint byte limit exceeded.');
|
|
198
|
-
hash.update(JSON.stringify(file));
|
|
199
|
-
hash.update(`:${content.length}:`);
|
|
200
|
-
hash.update(content);
|
|
201
|
-
}
|
|
202
|
-
return hash.digest('hex');
|
|
203
|
-
}
|
|
204
|
-
function merge(old, added, key) {
|
|
205
|
-
const entries = new Map(old.map(item => [key(item), item]));
|
|
206
|
-
for (const item of added) {
|
|
207
|
-
const previous = entries.get(key(item));
|
|
208
|
-
if (previous && Date.parse(previous.observedAt) > Date.parse(item.observedAt))
|
|
209
|
-
throw new Error('Older evidence cannot replace a newer observation.');
|
|
210
|
-
entries.set(key(item), item);
|
|
211
|
-
}
|
|
212
|
-
if (entries.size > 100)
|
|
213
|
-
throw new Error('Too many progress observations.');
|
|
214
|
-
return [...entries.values()];
|
|
215
|
-
}
|
|
216
|
-
function recoverDeadWriter(lock) {
|
|
217
|
-
const recovery = `${lock}.recovery`;
|
|
218
|
-
let guard;
|
|
219
|
-
try {
|
|
220
|
-
guard = openSync(recovery, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
221
|
-
}
|
|
222
|
-
catch {
|
|
223
|
-
return false;
|
|
224
|
-
}
|
|
225
|
-
try {
|
|
226
|
-
if (!existsSync(lock))
|
|
227
|
-
return true;
|
|
228
|
-
const entry = lstatSync(lock);
|
|
229
|
-
if (!entry.isFile() || entry.isSymbolicLink() || !process.getuid || entry.uid !== process.getuid())
|
|
230
|
-
return false;
|
|
231
|
-
let owner;
|
|
232
|
-
try {
|
|
233
|
-
owner = JSON.parse(safeRead(lock, 1024).toString('utf8'));
|
|
234
|
-
}
|
|
235
|
-
catch {
|
|
236
|
-
return false;
|
|
237
|
-
}
|
|
238
|
-
if (!owner || !Number.isSafeInteger(owner.pid) || owner.pid <= 0 || owner.hostname !== hostname())
|
|
239
|
-
return false;
|
|
240
|
-
try {
|
|
241
|
-
process.kill(owner.pid, 0);
|
|
242
|
-
return false;
|
|
243
|
-
}
|
|
244
|
-
catch (error) {
|
|
245
|
-
if (error.code !== 'ESRCH')
|
|
246
|
-
return false;
|
|
247
|
-
}
|
|
248
|
-
const current = lstatSync(lock);
|
|
249
|
-
if (current.ino !== entry.ino || current.dev !== entry.dev || current.uid !== entry.uid || !current.isFile() || current.isSymbolicLink())
|
|
250
|
-
return false;
|
|
251
|
-
unlinkSync(lock);
|
|
252
|
-
return true;
|
|
253
|
-
}
|
|
254
|
-
finally {
|
|
255
|
-
closeSync(guard);
|
|
256
|
-
unlinkSync(recovery);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
function acquireProgressLock(lock) {
|
|
260
|
-
const busy = () => new Error('Progress update busy or lock ownership unknown; retry after the writer finishes. Unknown/crashed recovery locks require confirming no writer is running before removal.');
|
|
261
|
-
for (let attempt = 0; attempt < 2; attempt++) {
|
|
262
|
-
if (existsSync(`${lock}.recovery`))
|
|
263
|
-
throw busy();
|
|
264
|
-
try {
|
|
265
|
-
return openSync(lock, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
if (error.code !== 'EEXIST')
|
|
269
|
-
throw error;
|
|
270
|
-
if (attempt || !recoverDeadWriter(lock))
|
|
271
|
-
throw busy();
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
throw busy();
|
|
275
|
-
}
|
|
276
|
-
/** Reports are caller assertions. Only actual checking adapters should use source: independent. */
|
|
277
|
-
export function reportProjectProgress(root, input) {
|
|
278
|
-
const report = validateProgressReport(input);
|
|
279
|
-
const destination = filePath(root, PROGRESS_FILE);
|
|
280
|
-
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
281
|
-
const lock = filePath(root, '.anhedral-progress.lock');
|
|
282
|
-
const fd = acquireProgressLock(lock);
|
|
283
|
-
try {
|
|
284
|
-
writeFileSync(fd, JSON.stringify({ pid: process.pid, hostname: hostname(), createdAt: new Date().toISOString() }));
|
|
285
|
-
fsyncSync(fd);
|
|
286
|
-
const current = readProjectProgress(root) ?? { schemaVersion: 1, revision: 0, updatedAt: new Date().toISOString(), environments: [] };
|
|
287
|
-
if (report.revision !== current.revision)
|
|
288
|
-
throw new Error('Progress revision conflict; reload before reporting.');
|
|
289
|
-
for (const item of [...(report.observations ?? []), ...(report.discovery ?? [])])
|
|
290
|
-
if (Date.parse(item.observedAt) > Date.now() + 300000)
|
|
291
|
-
throw new Error('Observation timestamp is in the future.');
|
|
292
|
-
const existing = current.environments.find(env => env.id === report.environment) ?? { id: report.environment, observations: [], discovery: [], blockers: [] };
|
|
293
|
-
const next = { ...existing, observations: merge(existing.observations, report.observations ?? [], item => JSON.stringify([item.capability, item.milestone, item.source, item.accountRef, item.resourceRef])), discovery: merge(existing.discovery, report.discovery ?? [], item => JSON.stringify([item.provider, item.route, item.check, item.source, item.accountRef])) };
|
|
294
|
-
if (report.blockers !== undefined)
|
|
295
|
-
next.blockers = report.blockers;
|
|
296
|
-
for (const key of ['nextAction', 'previewUrl', 'liveUrl'])
|
|
297
|
-
if (report[key] !== undefined) {
|
|
298
|
-
if (report[key] === null)
|
|
299
|
-
delete next[key];
|
|
300
|
-
else
|
|
301
|
-
next[key] = report[key];
|
|
302
|
-
}
|
|
303
|
-
const result = validateProjectProgress({ ...current, revision: current.revision + 1, updatedAt: new Date().toISOString(), environments: [...current.environments.filter(env => env.id !== next.id), next] });
|
|
304
|
-
const serialized = JSON.stringify(result, null, 2) + '\n';
|
|
305
|
-
if (Buffer.byteLength(serialized) > MAX_BYTES)
|
|
306
|
-
throw new Error('Progress record exceeds size limit.');
|
|
307
|
-
const output = openSync(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
308
|
-
try {
|
|
309
|
-
writeFileSync(output, serialized);
|
|
310
|
-
fsyncSync(output);
|
|
311
|
-
}
|
|
312
|
-
finally {
|
|
313
|
-
closeSync(output);
|
|
314
|
-
}
|
|
315
|
-
renameSync(temporary, destination);
|
|
316
|
-
return result;
|
|
317
|
-
}
|
|
318
|
-
finally {
|
|
319
|
-
closeSync(fd);
|
|
320
|
-
if (existsSync(temporary))
|
|
321
|
-
unlinkSync(temporary);
|
|
322
|
-
unlinkSync(lock);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
export function evaluateProjectProgress(record, fingerprint, now = Date.now()) {
|
|
326
|
-
return { ...record, environments: record.environments.map(env => ({ ...env, observations: env.observations.map(item => ({ ...item, stale: item.configurationFingerprint !== fingerprint })), discovery: env.discovery.map(item => ({ ...item, stale: Date.parse(item.expiresAt) <= now })) })) };
|
|
327
|
-
}
|
package/dist/project.d.ts
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const STANDARD_PRODUCTS: readonly ["next", "expo", "electron", "wxt", "hono", "neon", "d1", "local-data", "clerk", "better-auth", "r2", "kv", "realtime", "queues", "cron", "workflows", "openai", "ai-sdk", "workers-ai", "resend", "stripe", "revenuecat", "sentry", "basin", "posthog", "styling"];
|
|
2
|
-
export type StandardProduct = typeof STANDARD_PRODUCTS[number];
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export const STANDARD_PRODUCTS = [
|
|
2
|
-
'next', 'expo', 'electron', 'wxt', 'hono', 'neon', 'd1', 'local-data',
|
|
3
|
-
'clerk', 'better-auth', 'r2', 'kv', 'realtime', 'queues', 'cron', 'workflows',
|
|
4
|
-
'openai', 'ai-sdk', 'workers-ai', 'resend', 'stripe', 'revenuecat',
|
|
5
|
-
'sentry', 'basin', 'posthog', 'styling',
|
|
6
|
-
];
|
|
File without changes
|
|
File without changes
|