@volter/world-runtime 3.0.87 → 3.0.89
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/dist/src/cli-commands.js +4 -5
- package/dist/src/clock.d.ts +3 -0
- package/dist/src/clock.js +18 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/init.js +41 -49
- package/dist/src/runtime.d.ts +1 -1
- package/dist/src/runtime.js +1 -1
- package/package.json +4 -4
- package/src/cli-commands.ts +2 -3
- package/src/clock.ts +21 -0
- package/src/index.ts +1 -0
- package/src/init.ts +41 -49
- package/src/runtime.ts +1 -1
package/dist/src/cli-commands.js
CHANGED
|
@@ -4,10 +4,10 @@ import { optionValue, ownerOption } from "./cli-args.js";
|
|
|
4
4
|
import { printReady } from "./up-cli.js";
|
|
5
5
|
import { spawn, spawnSync } from 'node:child_process';
|
|
6
6
|
import { createRequire } from 'node:module';
|
|
7
|
-
import { existsSync,
|
|
7
|
+
import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
|
-
import {
|
|
10
|
-
import { activateScript, branchWorld, checkoutWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, resetWorld, seedWorld, worldOrigin, approveWorldChangeset, checkPrerequisites, coverWorld, createWorldChangeset, diffWorld, doctorWorld, downWorld, findWorldChangeset, formatCoverageReport, formatInitReport, formatPrerequisiteChecks, formatProjectInspection, initWorld, inspectProject, isRemoteWorldRef, listWorldChangesets, listWorlds, markWorld, migrateWorldConfig, readReflectRoutes, reflectRoutesPath, replayWorldChangeset, resolveWorldRef, attachWorld, retireWorldConsumers, runWorld, shareWorldServices, shellWorld, startReflectFront, startReflectResolver, statusWorld, statusWorldChangeset, tailWorldActions, unshareWorld, urlsWorld, urlWorld, verifyWorldChangeset, worldManifest, writeReflectRoutes, readReflectManifest, writeReflectManifest, clearReflectManifest, composeOverrideForReflect, splitDockerComposeArgs, dockerComposeWithOverride, ATTACHED_CA_PATH } from "./index.js";
|
|
9
|
+
import { join, resolve } from 'node:path';
|
|
10
|
+
import { activateScript, branchWorld, checkoutWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, resetWorld, seedWorld, worldOrigin, approveWorldChangeset, writeWorldClock, checkPrerequisites, coverWorld, createWorldChangeset, diffWorld, doctorWorld, downWorld, findWorldChangeset, formatCoverageReport, formatInitReport, formatPrerequisiteChecks, formatProjectInspection, initWorld, inspectProject, isRemoteWorldRef, listWorldChangesets, listWorlds, markWorld, migrateWorldConfig, readReflectRoutes, reflectRoutesPath, replayWorldChangeset, resolveWorldRef, attachWorld, retireWorldConsumers, runWorld, shareWorldServices, shellWorld, startReflectFront, startReflectResolver, statusWorld, statusWorldChangeset, tailWorldActions, unshareWorld, urlsWorld, urlWorld, verifyWorldChangeset, worldManifest, writeReflectRoutes, readReflectManifest, writeReflectManifest, clearReflectManifest, composeOverrideForReflect, splitDockerComposeArgs, dockerComposeWithOverride, ATTACHED_CA_PATH } from "./index.js";
|
|
11
11
|
import { formatApplication, formatChangeset, formatRebaseReport, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, parseVerifierExpression } from '@volter/world-core/runtime';
|
|
12
12
|
import { advertiseWorldManifest, fetchRemoteManifest, MANIFEST_PATH, remoteAttachEnv, startManifestServer } from "./serve.js";
|
|
13
13
|
import { clockFile, instanceDir, pruneWorlds, withoutVaultSignIn } from "./runtime.js";
|
|
@@ -539,8 +539,7 @@ export async function main() {
|
|
|
539
539
|
const file = clockFile(rootDir, name);
|
|
540
540
|
const current = existsSync(file) ? clockForm.parseClock(readFileSync(file, 'utf8')) : null;
|
|
541
541
|
const write = (clock) => {
|
|
542
|
-
|
|
543
|
-
writeFileSync(file, `${clockForm.formatClock(clock)}\n`);
|
|
542
|
+
writeWorldClock(name, rootDir, clock);
|
|
544
543
|
// the World's time now, bare, as scripts read it (`show` says which form)
|
|
545
544
|
console.log(new Date(clockForm.clockNowMs(clock, Date.now())).toISOString());
|
|
546
545
|
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// The programmatic counterpart of the operator's clock command. Callers own their World root; only invoked
|
|
2
|
+
// operations read or write it. Formatting belongs to the kernel's shared clock contract.
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
import { clockFile } from "./runtime.js";
|
|
7
|
+
const clockForm = createRequire(import.meta.url)('@volter/world-core/world-clock');
|
|
8
|
+
export function writeWorldClock(name, root, clock) {
|
|
9
|
+
const file = clockFile(root, name);
|
|
10
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
11
|
+
writeFileSync(file, `${clockForm.formatClock(clock)}\n`);
|
|
12
|
+
}
|
|
13
|
+
export function setWorldClock(name, root, at) {
|
|
14
|
+
const parsed = Date.parse(at);
|
|
15
|
+
if (Number.isNaN(parsed))
|
|
16
|
+
throw new Error(`clock set: ${JSON.stringify(at)} is not an ISO-8601 instant`);
|
|
17
|
+
writeWorldClock(name, root, { kind: 'frozen', at: parsed });
|
|
18
|
+
}
|
package/dist/src/index.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ export type { CoverageReport, CoverageRow, CoverageStatus, CoverageOptions, Repo
|
|
|
22
22
|
export { formatInitReport, initWorld, packCatalog, planWorldInit, renderEnvFile, writeWorldInit } from './init.js';
|
|
23
23
|
export { STATE_GITIGNORE } from './init.js';
|
|
24
24
|
export { clockFile, instanceDir } from './runtime.js';
|
|
25
|
+
export { setWorldClock, writeWorldClock } from './clock.js';
|
|
25
26
|
export { checkoutCatalog, findInstalledPackage, installedCatalog, resolveCatalog, resolveServicePackage, twinPackageName, TWIN_PACKAGE_PREFIX, type Catalog, type CatalogKind, packCli, packEntry } from './catalog.js';
|
|
26
27
|
export { ISSUE_ENV_PREFIX, MINT_ENV } from './schema.js';
|
|
27
28
|
export type { EnvDisposition, InitEnvRow, InitInfraStub, InitOptions, InitPlan, InitResult, InitVendorPlan, InitWiring, } from './init.js';
|
package/dist/src/index.js
CHANGED
|
@@ -20,6 +20,7 @@ export { coverWorld, detectRepoVendors, envNameVendor, formatCoverageReport, inj
|
|
|
20
20
|
export { formatInitReport, initWorld, packCatalog, planWorldInit, renderEnvFile, writeWorldInit } from "./init.js";
|
|
21
21
|
export { STATE_GITIGNORE } from "./init.js";
|
|
22
22
|
export { clockFile, instanceDir } from "./runtime.js";
|
|
23
|
+
export { setWorldClock, writeWorldClock } from "./clock.js";
|
|
23
24
|
export { checkoutCatalog, findInstalledPackage, installedCatalog, resolveCatalog, resolveServicePackage, twinPackageName, TWIN_PACKAGE_PREFIX, packCli, packEntry } from "./catalog.js";
|
|
24
25
|
export { ISSUE_ENV_PREFIX, MINT_ENV } from "./schema.js";
|
|
25
26
|
export { deriveWorldManifest, findWorldRefFile, isRemoteWorldRef, resolveWorldRef, WORLD_REF_FILE, worldManifest, } from "./attach.js";
|
package/dist/src/init.js
CHANGED
|
@@ -1158,66 +1158,58 @@ export async function seed(): Promise<void> {
|
|
|
1158
1158
|
if ((process.env.VOLTER_SEED_SKIP ?? '').split(',').includes('${vendor}')) return;
|
|
1159
1159
|
const base = process.env.${stem}_TWIN_URL;
|
|
1160
1160
|
if (!base) throw new Error('${stem}_TWIN_URL is not set — bring the world up and run the seed with its env (volter-world attach/run)');
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1161
|
+
const clients = new Map<string, SeedBrowser>();
|
|
1162
|
+
const snapshots = new Map<string, Awaited<ReturnType<SeedBrowser['state']>>>();
|
|
1163
|
+
let browserHelper: typeof import('@volter/world-core/browser') | undefined;
|
|
1164
|
+
const browserCalls = ('browserSeed' in defaults ? defaults.browserSeed as typeof calls : undefined) ?? [];
|
|
1165
|
+
const requests = [...calls, ...browserCalls];
|
|
1164
1166
|
const clock = process.env.TWIN_WORLD_CLOCK_FILE;
|
|
1165
|
-
const
|
|
1167
|
+
const dated = requests.some(call => call.at !== undefined);
|
|
1168
|
+
const found = dated && clock && existsSync(clock) ? readFileSync(clock, 'utf8') : undefined;
|
|
1169
|
+
const restoreClock = (): void => {
|
|
1170
|
+
if (!dated || !clock) return;
|
|
1171
|
+
if (found === undefined) rmSync(clock, { force: true }); else writeFileSync(clock, found);
|
|
1172
|
+
};
|
|
1166
1173
|
try {
|
|
1167
|
-
for (const call of
|
|
1174
|
+
for (const call of requests) {
|
|
1168
1175
|
if (call.at !== undefined) {
|
|
1169
1176
|
if (!clock) throw new Error('${vendor} seed: a dated call needs the World clock (TWIN_WORLD_CLOCK_FILE): run the seed attached to the World');
|
|
1170
1177
|
writeFileSync(clock, \`\${new Date(call.at).toISOString()}\n\`);
|
|
1178
|
+
} else restoreClock();
|
|
1179
|
+
if (call.browser) {
|
|
1180
|
+
if (!snapshots.has(call.browser)) {
|
|
1181
|
+
const door = process.env.VOLTER_WORLD_BROWSER_DOOR;
|
|
1182
|
+
const token = process.env.VOLTER_WORLD_BROWSER_TOKEN;
|
|
1183
|
+
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1184
|
+
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1185
|
+
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1186
|
+
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1187
|
+
}
|
|
1188
|
+
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1189
|
+
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1190
|
+
});
|
|
1191
|
+
const initial = snapshots.get(call.browser)!;
|
|
1192
|
+
const hostname = new URL(call.path).hostname;
|
|
1193
|
+
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1194
|
+
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1195
|
+
}
|
|
1196
|
+
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1197
|
+
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
|
1198
|
+
const body = call.body === undefined ? undefined : form
|
|
1199
|
+
? new URLSearchParams(Object.entries(call.body as Record<string, unknown>).map(([key, value]) => [key, String(value)]))
|
|
1200
|
+
: JSON.stringify(call.body);
|
|
1201
|
+
if (call.browser && !clients.has(call.browser)) {
|
|
1202
|
+
clients.set(call.browser, await browserHelper!.browser(call.browser));
|
|
1171
1203
|
}
|
|
1172
|
-
const
|
|
1204
|
+
const client = call.browser ? clients.get(call.browser)! : null;
|
|
1205
|
+
const res = await (client ? client.fetch : fetch)(client ? call.path : \`\${base.replace(/\\/$/, '')}\${call.path}\`, {
|
|
1173
1206
|
method: call.method,
|
|
1174
|
-
headers
|
|
1175
|
-
...(call.body !== undefined ? { body: JSON.stringify(call.body) } : {}),
|
|
1207
|
+
headers, ...(body === undefined ? {} : { body }),
|
|
1176
1208
|
});
|
|
1177
1209
|
if (!res.ok) throw new Error(\`${vendor} seed: \${call.method} \${call.path}: \${res.status} \${await res.text()}\`);
|
|
1178
1210
|
}
|
|
1179
1211
|
} finally {
|
|
1180
|
-
|
|
1181
|
-
if (found === undefined) rmSync(clock, { force: true }); else writeFileSync(clock, found);
|
|
1182
|
-
}
|
|
1183
|
-
=======
|
|
1184
|
-
const clients = new Map<string, SeedBrowser>();
|
|
1185
|
-
const snapshots = new Map<string, Awaited<ReturnType<SeedBrowser['state']>>>();
|
|
1186
|
-
let browserHelper: typeof import('@volter/world-core/browser') | undefined;
|
|
1187
|
-
const browserCalls = ('browserSeed' in defaults ? defaults.browserSeed as typeof calls : undefined) ?? [];
|
|
1188
|
-
for (const call of [...calls, ...browserCalls]) {
|
|
1189
|
-
if (call.browser) {
|
|
1190
|
-
if (!snapshots.has(call.browser)) {
|
|
1191
|
-
const door = process.env.VOLTER_WORLD_BROWSER_DOOR;
|
|
1192
|
-
const token = process.env.VOLTER_WORLD_BROWSER_TOKEN;
|
|
1193
|
-
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1194
|
-
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1195
|
-
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1196
|
-
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1197
|
-
}
|
|
1198
|
-
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1199
|
-
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1200
|
-
});
|
|
1201
|
-
const initial = snapshots.get(call.browser)!;
|
|
1202
|
-
const hostname = new URL(call.path).hostname;
|
|
1203
|
-
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1204
|
-
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1205
|
-
}
|
|
1206
|
-
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1207
|
-
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
|
1208
|
-
const body = call.body === undefined ? undefined : form
|
|
1209
|
-
? new URLSearchParams(Object.entries(call.body as Record<string, unknown>).map(([key, value]) => [key, String(value)]))
|
|
1210
|
-
: JSON.stringify(call.body);
|
|
1211
|
-
if (call.browser && !clients.has(call.browser)) {
|
|
1212
|
-
clients.set(call.browser, await browserHelper!.browser(call.browser));
|
|
1213
|
-
}
|
|
1214
|
-
const client = call.browser ? clients.get(call.browser)! : null;
|
|
1215
|
-
const res = await (client ? client.fetch : fetch)(client ? call.path : \`\${base.replace(/\\/$/, '')}\${call.path}\`, {
|
|
1216
|
-
method: call.method,
|
|
1217
|
-
headers, ...(body === undefined ? {} : { body }),
|
|
1218
|
-
});
|
|
1219
|
-
if (!res.ok) throw new Error(\`${vendor} seed: \${call.method} \${call.path}: \${res.status} \${await res.text()}\`);
|
|
1220
|
-
>>>>>>> origin/main
|
|
1212
|
+
restoreClock();
|
|
1221
1213
|
}
|
|
1222
1214
|
}
|
|
1223
1215
|
`;
|
package/dist/src/runtime.d.ts
CHANGED
|
@@ -67,7 +67,7 @@ export type WorldUrlInfo = {
|
|
|
67
67
|
* the instance dir — one layout owner, no duplicated path knowledge. */
|
|
68
68
|
/** THE WORLD CLOCK file (TWIN-PROGRAMMING-MODEL P3): a single frozen ISO instant every twin
|
|
69
69
|
* stamps from (kernel worldNow()); absent → real wall-clock. Written only by the
|
|
70
|
-
* `volter-world clock` operator door — time is physics, advanced explicitly, never drifting. */
|
|
70
|
+
* `volter-world clock` operator door or its runtime API — time is physics, advanced explicitly, never drifting. */
|
|
71
71
|
export declare function clockFile(root: string, name: string): string;
|
|
72
72
|
export declare function instanceDir(root: string, name: string): string;
|
|
73
73
|
export type SharingClaim = {
|
package/dist/src/runtime.js
CHANGED
|
@@ -290,7 +290,7 @@ function assertSafeWorldName(name) {
|
|
|
290
290
|
* the instance dir — one layout owner, no duplicated path knowledge. */
|
|
291
291
|
/** THE WORLD CLOCK file (TWIN-PROGRAMMING-MODEL P3): a single frozen ISO instant every twin
|
|
292
292
|
* stamps from (kernel worldNow()); absent → real wall-clock. Written only by the
|
|
293
|
-
* `volter-world clock` operator door — time is physics, advanced explicitly, never drifting. */
|
|
293
|
+
* `volter-world clock` operator door or its runtime API — time is physics, advanced explicitly, never drifting. */
|
|
294
294
|
export function clockFile(root, name) {
|
|
295
295
|
return join(instanceDir(root, name), 'clock');
|
|
296
296
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@volter/world-runtime",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.89",
|
|
4
4
|
"description": "World configs for twins: boot named local runtimes, allocate ports, generate world.env/instance.json, and run apps against fake-key twin worlds.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"twin",
|
|
@@ -69,14 +69,14 @@
|
|
|
69
69
|
"node": ">=22.3"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
72
|
-
"@volter/world-core": "3.0.
|
|
73
|
-
"@volter/world-console": "3.0.
|
|
72
|
+
"@volter/world-core": "3.0.89",
|
|
73
|
+
"@volter/world-console": "3.0.84"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"@electric-sql/pglite": "0.5.8",
|
|
77
77
|
"@electric-sql/pglite-pgvector": "0.0.9",
|
|
78
78
|
"@volter/world-access": "3.0.1",
|
|
79
|
-
"@volter/world-core": "3.0.
|
|
79
|
+
"@volter/world-core": "3.0.89",
|
|
80
80
|
"mongodb": "~6.20.0",
|
|
81
81
|
"pg-gateway": "0.3.0-beta.4",
|
|
82
82
|
"smol-toml": "^1.8.0",
|
package/src/cli-commands.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { createRequire } from 'node:module';
|
|
|
7
7
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
9
|
import { dirname, join, resolve, resolve as resolvePath } from 'node:path';
|
|
10
|
-
import { activateScript, branchWorld, checkoutWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, resetWorld, seedWorld, worldOrigin, approveWorldChangeset, checkPrerequisites, coverWorld, createWorldChangeset, diffWorld, doctorWorld, downWorld, findWorldChangeset, formatCoverageReport, formatInitReport, formatPrerequisiteChecks, formatProjectInspection, initWorld, inspectProject, isRemoteWorldRef, listWorldChangesets, listWorlds, markWorld, migrateWorldConfig, readReflectRoutes, reflectRoutesPath, replayWorldChangeset, resolveWorldRef, attachWorld, retireWorldConsumers, runWorld, shareWorldServices, shellWorld, startReflectFront, startReflectResolver, statusWorld, statusWorldChangeset, tailWorldActions, unshareWorld, upWorld, urlsWorld, urlWorld, verifyWorldChangeset, worldManifest, writeReflectRoutes, readReflectManifest, writeReflectManifest, clearReflectManifest, composeOverrideForReflect, splitDockerComposeArgs, dockerComposeWithOverride, ATTACHED_CA_PATH } from './index.ts';
|
|
10
|
+
import { activateScript, branchWorld, checkoutWorld, fetchFromOrigin, pushWorldChangeset, rebaseWorldChangeset, resetWorld, seedWorld, worldOrigin, approveWorldChangeset, writeWorldClock, checkPrerequisites, coverWorld, createWorldChangeset, diffWorld, doctorWorld, downWorld, findWorldChangeset, formatCoverageReport, formatInitReport, formatPrerequisiteChecks, formatProjectInspection, initWorld, inspectProject, isRemoteWorldRef, listWorldChangesets, listWorlds, markWorld, migrateWorldConfig, readReflectRoutes, reflectRoutesPath, replayWorldChangeset, resolveWorldRef, attachWorld, retireWorldConsumers, runWorld, shareWorldServices, shellWorld, startReflectFront, startReflectResolver, statusWorld, statusWorldChangeset, tailWorldActions, unshareWorld, upWorld, urlsWorld, urlWorld, verifyWorldChangeset, worldManifest, writeReflectRoutes, readReflectManifest, writeReflectManifest, clearReflectManifest, composeOverrideForReflect, splitDockerComposeArgs, dockerComposeWithOverride, ATTACHED_CA_PATH } from './index.ts';
|
|
11
11
|
import { formatApplication, formatChangeset, formatRebaseReport, formatChangesetStatus, formatLedgerDelta, formatReplayReport, formatVerification, parseVerifierExpression } from '@volter/world-core/runtime';
|
|
12
12
|
import { advertiseWorldManifest, fetchRemoteManifest, MANIFEST_PATH, remoteAttachEnv, startManifestServer } from './serve.ts';
|
|
13
13
|
import type { WorldManifest } from './attach.ts';
|
|
@@ -491,8 +491,7 @@ export async function main(): Promise<void> {
|
|
|
491
491
|
const file = clockFile(rootDir, name);
|
|
492
492
|
const current = existsSync(file) ? clockForm.parseClock(readFileSync(file, 'utf8')) : null;
|
|
493
493
|
const write = (clock: Parameters<typeof clockForm.formatClock>[0]) => {
|
|
494
|
-
|
|
495
|
-
writeFileSync(file, `${clockForm.formatClock(clock)}\n`);
|
|
494
|
+
writeWorldClock(name, rootDir, clock);
|
|
496
495
|
// the World's time now, bare, as scripts read it (`show` says which form)
|
|
497
496
|
console.log(new Date(clockForm.clockNowMs(clock, Date.now())).toISOString());
|
|
498
497
|
};
|
package/src/clock.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// The programmatic counterpart of the operator's clock command. Callers own their World root; only invoked
|
|
2
|
+
// operations read or write it. Formatting belongs to the kernel's shared clock contract.
|
|
3
|
+
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { dirname } from 'node:path';
|
|
6
|
+
import type { WorldClock } from '@volter/world-core/world-clock';
|
|
7
|
+
import { clockFile } from './runtime.ts';
|
|
8
|
+
|
|
9
|
+
const clockForm = createRequire(import.meta.url)('@volter/world-core/world-clock') as typeof import('@volter/world-core/world-clock');
|
|
10
|
+
|
|
11
|
+
export function writeWorldClock(name: string, root: string, clock: WorldClock): void {
|
|
12
|
+
const file = clockFile(root, name);
|
|
13
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
14
|
+
writeFileSync(file, `${clockForm.formatClock(clock)}\n`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setWorldClock(name: string, root: string, at: string): void {
|
|
18
|
+
const parsed = Date.parse(at);
|
|
19
|
+
if (Number.isNaN(parsed)) throw new Error(`clock set: ${JSON.stringify(at)} is not an ISO-8601 instant`);
|
|
20
|
+
writeWorldClock(name, root, { kind: 'frozen', at: parsed });
|
|
21
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -100,6 +100,7 @@ export type { CoverageReport, CoverageRow, CoverageStatus, CoverageOptions, Repo
|
|
|
100
100
|
export { formatInitReport, initWorld, packCatalog, planWorldInit, renderEnvFile, writeWorldInit } from './init.ts';
|
|
101
101
|
export { STATE_GITIGNORE } from './init.ts';
|
|
102
102
|
export { clockFile, instanceDir } from './runtime.ts';
|
|
103
|
+
export { setWorldClock, writeWorldClock } from './clock.ts';
|
|
103
104
|
export { checkoutCatalog, findInstalledPackage, installedCatalog, resolveCatalog, resolveServicePackage, twinPackageName, TWIN_PACKAGE_PREFIX, type Catalog, type CatalogKind, packCli, packEntry } from './catalog.ts';
|
|
104
105
|
export { ISSUE_ENV_PREFIX, MINT_ENV } from './schema.ts';
|
|
105
106
|
export type {
|
package/src/init.ts
CHANGED
|
@@ -1347,66 +1347,58 @@ export async function seed(): Promise<void> {
|
|
|
1347
1347
|
if ((process.env.VOLTER_SEED_SKIP ?? '').split(',').includes('${vendor}')) return;
|
|
1348
1348
|
const base = process.env.${stem}_TWIN_URL;
|
|
1349
1349
|
if (!base) throw new Error('${stem}_TWIN_URL is not set — bring the world up and run the seed with its env (volter-world attach/run)');
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1350
|
+
const clients = new Map<string, SeedBrowser>();
|
|
1351
|
+
const snapshots = new Map<string, Awaited<ReturnType<SeedBrowser['state']>>>();
|
|
1352
|
+
let browserHelper: typeof import('@volter/world-core/browser') | undefined;
|
|
1353
|
+
const browserCalls = ('browserSeed' in defaults ? defaults.browserSeed as typeof calls : undefined) ?? [];
|
|
1354
|
+
const requests = [...calls, ...browserCalls];
|
|
1353
1355
|
const clock = process.env.TWIN_WORLD_CLOCK_FILE;
|
|
1354
|
-
const
|
|
1356
|
+
const dated = requests.some(call => call.at !== undefined);
|
|
1357
|
+
const found = dated && clock && existsSync(clock) ? readFileSync(clock, 'utf8') : undefined;
|
|
1358
|
+
const restoreClock = (): void => {
|
|
1359
|
+
if (!dated || !clock) return;
|
|
1360
|
+
if (found === undefined) rmSync(clock, { force: true }); else writeFileSync(clock, found);
|
|
1361
|
+
};
|
|
1355
1362
|
try {
|
|
1356
|
-
for (const call of
|
|
1363
|
+
for (const call of requests) {
|
|
1357
1364
|
if (call.at !== undefined) {
|
|
1358
1365
|
if (!clock) throw new Error('${vendor} seed: a dated call needs the World clock (TWIN_WORLD_CLOCK_FILE): run the seed attached to the World');
|
|
1359
1366
|
writeFileSync(clock, \`\${new Date(call.at).toISOString()}\n\`);
|
|
1367
|
+
} else restoreClock();
|
|
1368
|
+
if (call.browser) {
|
|
1369
|
+
if (!snapshots.has(call.browser)) {
|
|
1370
|
+
const door = process.env.VOLTER_WORLD_BROWSER_DOOR;
|
|
1371
|
+
const token = process.env.VOLTER_WORLD_BROWSER_TOKEN;
|
|
1372
|
+
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1373
|
+
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1374
|
+
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1375
|
+
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1376
|
+
}
|
|
1377
|
+
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1378
|
+
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1379
|
+
});
|
|
1380
|
+
const initial = snapshots.get(call.browser)!;
|
|
1381
|
+
const hostname = new URL(call.path).hostname;
|
|
1382
|
+
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1383
|
+
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1384
|
+
}
|
|
1385
|
+
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1386
|
+
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
|
1387
|
+
const body = call.body === undefined ? undefined : form
|
|
1388
|
+
? new URLSearchParams(Object.entries(call.body as Record<string, unknown>).map(([key, value]) => [key, String(value)]))
|
|
1389
|
+
: JSON.stringify(call.body);
|
|
1390
|
+
if (call.browser && !clients.has(call.browser)) {
|
|
1391
|
+
clients.set(call.browser, await browserHelper!.browser(call.browser));
|
|
1360
1392
|
}
|
|
1361
|
-
const
|
|
1393
|
+
const client = call.browser ? clients.get(call.browser)! : null;
|
|
1394
|
+
const res = await (client ? client.fetch : fetch)(client ? call.path : \`\${base.replace(/\\/$/, '')}\${call.path}\`, {
|
|
1362
1395
|
method: call.method,
|
|
1363
|
-
headers
|
|
1364
|
-
...(call.body !== undefined ? { body: JSON.stringify(call.body) } : {}),
|
|
1396
|
+
headers, ...(body === undefined ? {} : { body }),
|
|
1365
1397
|
});
|
|
1366
1398
|
if (!res.ok) throw new Error(\`${vendor} seed: \${call.method} \${call.path}: \${res.status} \${await res.text()}\`);
|
|
1367
1399
|
}
|
|
1368
1400
|
} finally {
|
|
1369
|
-
|
|
1370
|
-
if (found === undefined) rmSync(clock, { force: true }); else writeFileSync(clock, found);
|
|
1371
|
-
}
|
|
1372
|
-
=======
|
|
1373
|
-
const clients = new Map<string, SeedBrowser>();
|
|
1374
|
-
const snapshots = new Map<string, Awaited<ReturnType<SeedBrowser['state']>>>();
|
|
1375
|
-
let browserHelper: typeof import('@volter/world-core/browser') | undefined;
|
|
1376
|
-
const browserCalls = ('browserSeed' in defaults ? defaults.browserSeed as typeof calls : undefined) ?? [];
|
|
1377
|
-
for (const call of [...calls, ...browserCalls]) {
|
|
1378
|
-
if (call.browser) {
|
|
1379
|
-
if (!snapshots.has(call.browser)) {
|
|
1380
|
-
const door = process.env.VOLTER_WORLD_BROWSER_DOOR;
|
|
1381
|
-
const token = process.env.VOLTER_WORLD_BROWSER_TOKEN;
|
|
1382
|
-
if (!door || !token) throw new Error('browser defaults run in a World seed with its browsers door and credential');
|
|
1383
|
-
const held = await fetch(\`\${door}/\${encodeURIComponent(call.browser)}\`, { headers: { 'x-volter-token': token } });
|
|
1384
|
-
if (!held.ok && held.status !== 404) throw new Error(\`browser \${call.browser}: \${held.status} \${await held.text()}\`);
|
|
1385
|
-
snapshots.set(call.browser, held.ok ? await held.json() as Awaited<ReturnType<SeedBrowser['state']>> : { storageState: { cookies: [], origins: [] }, context: {} });
|
|
1386
|
-
}
|
|
1387
|
-
browserHelper ??= await import('@volter/world-core/browser').catch(error => {
|
|
1388
|
-
throw new Error('Install @volter/world-core 3.0.37 or later in this project to seed browsers.', { cause: error });
|
|
1389
|
-
});
|
|
1390
|
-
const initial = snapshots.get(call.browser)!;
|
|
1391
|
-
const hostname = new URL(call.path).hostname;
|
|
1392
|
-
if (initial.storageState.cookies.some(cookie => browserHelper!.cookieMatchesHost(cookie, hostname))
|
|
1393
|
-
|| initial.storageState.origins.some(origin => new URL(origin.origin).hostname === hostname)) continue;
|
|
1394
|
-
}
|
|
1395
|
-
const headers = { ...(call.body !== undefined ? { 'content-type': 'application/json' } : {}), ...(call.headers ?? {}) };
|
|
1396
|
-
const form = /^application\\/x-www-form-urlencoded\\b/i.test(new Headers(headers).get('content-type') ?? '');
|
|
1397
|
-
const body = call.body === undefined ? undefined : form
|
|
1398
|
-
? new URLSearchParams(Object.entries(call.body as Record<string, unknown>).map(([key, value]) => [key, String(value)]))
|
|
1399
|
-
: JSON.stringify(call.body);
|
|
1400
|
-
if (call.browser && !clients.has(call.browser)) {
|
|
1401
|
-
clients.set(call.browser, await browserHelper!.browser(call.browser));
|
|
1402
|
-
}
|
|
1403
|
-
const client = call.browser ? clients.get(call.browser)! : null;
|
|
1404
|
-
const res = await (client ? client.fetch : fetch)(client ? call.path : \`\${base.replace(/\\/$/, '')}\${call.path}\`, {
|
|
1405
|
-
method: call.method,
|
|
1406
|
-
headers, ...(body === undefined ? {} : { body }),
|
|
1407
|
-
});
|
|
1408
|
-
if (!res.ok) throw new Error(\`${vendor} seed: \${call.method} \${call.path}: \${res.status} \${await res.text()}\`);
|
|
1409
|
-
>>>>>>> origin/main
|
|
1401
|
+
restoreClock();
|
|
1410
1402
|
}
|
|
1411
1403
|
}
|
|
1412
1404
|
`;
|
package/src/runtime.ts
CHANGED
|
@@ -315,7 +315,7 @@ function assertSafeWorldName(name: string): void {
|
|
|
315
315
|
* the instance dir — one layout owner, no duplicated path knowledge. */
|
|
316
316
|
/** THE WORLD CLOCK file (TWIN-PROGRAMMING-MODEL P3): a single frozen ISO instant every twin
|
|
317
317
|
* stamps from (kernel worldNow()); absent → real wall-clock. Written only by the
|
|
318
|
-
* `volter-world clock` operator door — time is physics, advanced explicitly, never drifting. */
|
|
318
|
+
* `volter-world clock` operator door or its runtime API — time is physics, advanced explicitly, never drifting. */
|
|
319
319
|
export function clockFile(root: string, name: string): string {
|
|
320
320
|
return join(instanceDir(root, name), 'clock');
|
|
321
321
|
}
|