@forgecart/cli 2.202610102045.0 → 2.202610110825.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/dist/src/authorization/cli-unauthorized-refusal.d.ts +10 -4
- package/dist/src/authorization/cli-unauthorized-refusal.js +11 -3
- package/dist/src/authorization/cli-unauthorized-refusal.js.map +1 -1
- package/dist/src/cli.js +4 -0
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands/init.js +5 -47
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/sync.d.ts +14 -0
- package/dist/src/commands/sync.js +49 -0
- package/dist/src/commands/sync.js.map +1 -0
- package/dist/src/exit-ladder.d.ts +8 -1
- package/dist/src/exit-ladder.js +7 -0
- package/dist/src/exit-ladder.js.map +1 -1
- package/dist/src/sync/sync-api.error.d.ts +15 -0
- package/dist/src/sync/sync-api.error.js +26 -0
- package/dist/src/sync/sync-api.error.js.map +1 -0
- package/dist/src/sync/sync-divergence.d.ts +43 -0
- package/dist/src/sync/sync-divergence.js +104 -0
- package/dist/src/sync/sync-divergence.js.map +1 -0
- package/dist/src/sync/sync-dotenv.file.d.ts +36 -0
- package/dist/src/sync/sync-dotenv.file.js +95 -0
- package/dist/src/sync/sync-dotenv.file.js.map +1 -0
- package/dist/src/sync/sync-env.runner.d.ts +29 -0
- package/dist/src/sync/sync-env.runner.js +95 -0
- package/dist/src/sync/sync-env.runner.js.map +1 -0
- package/dist/src/sync/sync-record.file.d.ts +28 -0
- package/dist/src/sync/sync-record.file.js +86 -0
- package/dist/src/sync/sync-record.file.js.map +1 -0
- package/dist/src/sync/sync-repository.d.ts +110 -0
- package/dist/src/sync/sync-repository.js +396 -0
- package/dist/src/sync/sync-repository.js.map +1 -0
- package/dist/src/sync/sync-round.d.ts +95 -0
- package/dist/src/sync/sync-round.js +355 -0
- package/dist/src/sync/sync-round.js.map +1 -0
- package/dist/src/sync/sync-watch.d.ts +48 -0
- package/dist/src/sync/sync-watch.js +144 -0
- package/dist/src/sync/sync-watch.js.map +1 -0
- package/dist/src/sync/sync.runner.d.ts +37 -0
- package/dist/src/sync/sync.runner.js +121 -0
- package/dist/src/sync/sync.runner.js.map +1 -0
- package/dist/src/sync/type/sync.types.d.ts +159 -0
- package/dist/src/sync/type/sync.types.js +30 -0
- package/dist/src/sync/type/sync.types.js.map +1 -0
- package/dist/src/sync/workspace-sync.client.d.ts +85 -0
- package/dist/src/sync/workspace-sync.client.js +317 -0
- package/dist/src/sync/workspace-sync.client.js.map +1 -0
- package/dist/src/util/dotenv.util.d.ts +28 -0
- package/dist/src/util/dotenv.util.js +81 -0
- package/dist/src/util/dotenv.util.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CliCredentialKey, } from '../authorization/type/cli-authorization.types.js';
|
|
5
|
+
import { dotenvValue, mergeDotenv, merchantAssignments } from '../util/dotenv.util.js';
|
|
6
|
+
/**
|
|
7
|
+
* The laptop's `.env`, as `forgecart sync` carries it (#2781). Only merchant
|
|
8
|
+
* lines travel — the platform's `FORGECART_*` keys never cross — and they
|
|
9
|
+
* travel under init's merge rule: a key is refreshed in place, every other line
|
|
10
|
+
* is kept verbatim, nothing is deleted.
|
|
11
|
+
*
|
|
12
|
+
* A push carries the merchant lines changed since they last travelled, so a
|
|
13
|
+
* key the workspace changed meanwhile is not overwritten by a stale copy; a
|
|
14
|
+
* pull takes the workspace's lines for every key the laptop has not changed
|
|
15
|
+
* since, beside the run's four credentials — the `.env` a fresh pull writes.
|
|
16
|
+
* What travelled is recorded as a digest per key, never the value.
|
|
17
|
+
*/
|
|
18
|
+
export class SyncDotenvFile {
|
|
19
|
+
path;
|
|
20
|
+
constructor(storefront) {
|
|
21
|
+
this.path = join(storefront, '.env');
|
|
22
|
+
}
|
|
23
|
+
/** The merchant lines a push carries — every one changed since `recorded` — and the digests to record once they land. */
|
|
24
|
+
async outgoing(recorded) {
|
|
25
|
+
const assignments = merchantAssignments((await this.read()).split('\n'));
|
|
26
|
+
const digests = this.digestsOf(assignments);
|
|
27
|
+
const lines = [...assignments]
|
|
28
|
+
.filter(([key]) => recorded?.[key] !== digests[key])
|
|
29
|
+
.map(([, line]) => line);
|
|
30
|
+
return { lines, digests };
|
|
31
|
+
}
|
|
32
|
+
/** How many of the workspace's merchant lines a pull would take — every key the laptop has not changed since `recorded`. */
|
|
33
|
+
async takeable(workspaceLines, recorded) {
|
|
34
|
+
const changed = this.changedSince(await this.read(), recorded);
|
|
35
|
+
const keys = [...merchantAssignments(workspaceLines).keys()];
|
|
36
|
+
return keys.filter((key) => !changed.has(key)).length;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Take the workspace's merchant lines — for every key the laptop has not
|
|
40
|
+
* changed since `recorded` — and the run's four credentials into `.env`,
|
|
41
|
+
* under init's merge rule. A key the laptop changed keeps the laptop's value
|
|
42
|
+
* and the digest it last travelled with, so the next push still carries it.
|
|
43
|
+
*/
|
|
44
|
+
async take(workspaceLines, credentials, recorded) {
|
|
45
|
+
const existing = await this.read();
|
|
46
|
+
const changed = this.changedSince(existing, recorded);
|
|
47
|
+
const taken = [...merchantAssignments(workspaceLines)].filter(([key]) => !changed.has(key));
|
|
48
|
+
const managed = new Map([
|
|
49
|
+
...this.credentialEntries(credentials),
|
|
50
|
+
...taken.map(([key, line]) => [key, dotenvValue(line, key)]),
|
|
51
|
+
]);
|
|
52
|
+
const next = mergeDotenv(existing, managed);
|
|
53
|
+
if (next !== existing)
|
|
54
|
+
await writeFile(this.path, next, 'utf8');
|
|
55
|
+
const merged = this.digestsOf(merchantAssignments(next.split('\n')));
|
|
56
|
+
const digests = {};
|
|
57
|
+
for (const [key, digest] of Object.entries(merged)) {
|
|
58
|
+
const recordedDigest = changed.has(key) ? recorded?.[key] : digest;
|
|
59
|
+
if (recordedDigest !== undefined)
|
|
60
|
+
digests[key] = recordedDigest;
|
|
61
|
+
}
|
|
62
|
+
return { taken: taken.length, digests };
|
|
63
|
+
}
|
|
64
|
+
/** The keys whose line differs from the one that last travelled — none before a first sync. */
|
|
65
|
+
changedSince(content, recorded) {
|
|
66
|
+
if (recorded === null)
|
|
67
|
+
return new Set();
|
|
68
|
+
const digests = this.digestsOf(merchantAssignments(content.split('\n')));
|
|
69
|
+
return new Set(Object.keys(digests).filter((key) => recorded[key] !== digests[key]));
|
|
70
|
+
}
|
|
71
|
+
/** The four credentials as `.env` assignments, in the order init writes them. */
|
|
72
|
+
credentialEntries(credentials) {
|
|
73
|
+
return [
|
|
74
|
+
[CliCredentialKey.CHANNEL_TOKEN, credentials.channelToken],
|
|
75
|
+
[CliCredentialKey.SHOP_API_URL, credentials.shopApiUrl],
|
|
76
|
+
[CliCredentialKey.ADMIN_SECRET, credentials.adminSecret],
|
|
77
|
+
[CliCredentialKey.ADMIN_API_URL, credentials.adminApiUrl],
|
|
78
|
+
];
|
|
79
|
+
}
|
|
80
|
+
digestsOf(assignments) {
|
|
81
|
+
return Object.fromEntries([...assignments].map(([key, line]) => [key, createHash('sha256').update(line).digest('hex')]));
|
|
82
|
+
}
|
|
83
|
+
/** The `.env` text — empty when the storefront has none; any other failure propagates. */
|
|
84
|
+
async read() {
|
|
85
|
+
try {
|
|
86
|
+
return await readFile(this.path, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code === 'ENOENT')
|
|
90
|
+
return '';
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sync-dotenv.file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-dotenv.file.js","sourceRoot":"","sources":["../../../src/sync/sync-dotenv.file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { SyncEnvOptions } from './type/sync.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* `forgecart sync-env [name]` (#2781) — the workspace's side of `forgecart
|
|
4
|
+
* sync`, which the admin API runs in the pod: it reads the merchant lines of the
|
|
5
|
+
* storefront's `.env`, or, with `--merge`, merges the lines
|
|
6
|
+
* FORGECART_SYNC_ENV_LINES carries under init's merge rule, and prints ONE JSON
|
|
7
|
+
* report — the merchant lines as they stand, and how many keys the merge
|
|
8
|
+
* carried in. The platform's own `FORGECART_*` keys are never read out and
|
|
9
|
+
* never written: a laptop's never land, the pod's never leave.
|
|
10
|
+
*/
|
|
11
|
+
export declare class SyncEnvRunner {
|
|
12
|
+
private readonly name;
|
|
13
|
+
private readonly options;
|
|
14
|
+
/**
|
|
15
|
+
* @param name the storefront directory as given — the current directory when absent.
|
|
16
|
+
* @param options the run's flags.
|
|
17
|
+
*/
|
|
18
|
+
constructor(name: string | undefined, options: SyncEnvOptions);
|
|
19
|
+
run(): Promise<void>;
|
|
20
|
+
/** The merchant keys the variable carries, keyed to their values — platform keys and other lines dropped. */
|
|
21
|
+
private incoming;
|
|
22
|
+
private parseLines;
|
|
23
|
+
private malformedMessage;
|
|
24
|
+
private merchantLines;
|
|
25
|
+
/** The report the admin API parses: ONE line of JSON on stdout. */
|
|
26
|
+
private report;
|
|
27
|
+
/** The `.env` text — empty when the storefront has none; any other failure propagates. */
|
|
28
|
+
private read;
|
|
29
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join, resolve } from 'node:path';
|
|
3
|
+
import { print } from '../output.js';
|
|
4
|
+
import { dotenvValue, mergeDotenv, merchantAssignments } from '../util/dotenv.util.js';
|
|
5
|
+
/**
|
|
6
|
+
* The variable a merge's lines arrive in — the pod records a command's line,
|
|
7
|
+
* never its environment, so the lines never land in a record.
|
|
8
|
+
*/
|
|
9
|
+
const SYNC_ENV_LINES_VARIABLE = 'FORGECART_SYNC_ENV_LINES';
|
|
10
|
+
/**
|
|
11
|
+
* `forgecart sync-env [name]` (#2781) — the workspace's side of `forgecart
|
|
12
|
+
* sync`, which the admin API runs in the pod: it reads the merchant lines of the
|
|
13
|
+
* storefront's `.env`, or, with `--merge`, merges the lines
|
|
14
|
+
* FORGECART_SYNC_ENV_LINES carries under init's merge rule, and prints ONE JSON
|
|
15
|
+
* report — the merchant lines as they stand, and how many keys the merge
|
|
16
|
+
* carried in. The platform's own `FORGECART_*` keys are never read out and
|
|
17
|
+
* never written: a laptop's never land, the pod's never leave.
|
|
18
|
+
*/
|
|
19
|
+
export class SyncEnvRunner {
|
|
20
|
+
name;
|
|
21
|
+
options;
|
|
22
|
+
/**
|
|
23
|
+
* @param name the storefront directory as given — the current directory when absent.
|
|
24
|
+
* @param options the run's flags.
|
|
25
|
+
*/
|
|
26
|
+
constructor(name, options) {
|
|
27
|
+
this.name = name;
|
|
28
|
+
this.options = options;
|
|
29
|
+
}
|
|
30
|
+
async run() {
|
|
31
|
+
const envPath = join(this.name ? resolve(process.cwd(), this.name) : process.cwd(), '.env');
|
|
32
|
+
const existing = await this.read(envPath);
|
|
33
|
+
if (this.options.merge !== true) {
|
|
34
|
+
this.report({ lines: this.merchantLines(existing), merged: 0 });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const managed = this.incoming(process.env[SYNC_ENV_LINES_VARIABLE]);
|
|
38
|
+
if (managed.size === 0) {
|
|
39
|
+
this.report({ lines: this.merchantLines(existing), merged: 0 });
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const next = mergeDotenv(existing, managed);
|
|
43
|
+
if (next !== existing)
|
|
44
|
+
await writeFile(envPath, next, 'utf8');
|
|
45
|
+
this.report({ lines: this.merchantLines(next), merged: managed.size });
|
|
46
|
+
}
|
|
47
|
+
/** The merchant keys the variable carries, keyed to their values — platform keys and other lines dropped. */
|
|
48
|
+
incoming(raw) {
|
|
49
|
+
const lines = this.parseLines(raw);
|
|
50
|
+
return new Map([...merchantAssignments(lines)].map(([key, line]) => [
|
|
51
|
+
key,
|
|
52
|
+
dotenvValue(line, key),
|
|
53
|
+
]));
|
|
54
|
+
}
|
|
55
|
+
parseLines(raw) {
|
|
56
|
+
if (raw === undefined)
|
|
57
|
+
throw new Error(this.malformedMessage());
|
|
58
|
+
let parsed;
|
|
59
|
+
try {
|
|
60
|
+
parsed = JSON.parse(raw);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(this.malformedMessage(), { cause: error });
|
|
64
|
+
}
|
|
65
|
+
if (!Array.isArray(parsed))
|
|
66
|
+
throw new Error(this.malformedMessage());
|
|
67
|
+
const items = parsed;
|
|
68
|
+
if (!items.every((item) => typeof item === 'string')) {
|
|
69
|
+
throw new Error(this.malformedMessage());
|
|
70
|
+
}
|
|
71
|
+
return items;
|
|
72
|
+
}
|
|
73
|
+
malformedMessage() {
|
|
74
|
+
return `--merge reads ${SYNC_ENV_LINES_VARIABLE}, which must hold a JSON list of KEY=value lines.`;
|
|
75
|
+
}
|
|
76
|
+
merchantLines(content) {
|
|
77
|
+
return [...merchantAssignments(content.split('\n')).values()];
|
|
78
|
+
}
|
|
79
|
+
/** The report the admin API parses: ONE line of JSON on stdout. */
|
|
80
|
+
report(report) {
|
|
81
|
+
print(JSON.stringify(report));
|
|
82
|
+
}
|
|
83
|
+
/** The `.env` text — empty when the storefront has none; any other failure propagates. */
|
|
84
|
+
async read(path) {
|
|
85
|
+
try {
|
|
86
|
+
return await readFile(path, 'utf8');
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code === 'ENOENT')
|
|
90
|
+
return '';
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=sync-env.runner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-env.runner.js","sourceRoot":"","sources":["../../../src/sync/sync-env.runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SyncRecord } from './type/sync.types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The last sync of one storefront (#2781), kept in `.forgecart/sync/state.json`
|
|
4
|
+
* beside the private repository: the channel it ran against — a digest of the
|
|
5
|
+
* admin API and the channel token, never the token — the serving commit the
|
|
6
|
+
* laptop's tree last matched, and a digest of every merchant `.env` line as it
|
|
7
|
+
* last travelled. A record of another channel is no record: a storefront pointed
|
|
8
|
+
* at another channel syncs as a first sync.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SyncRecordFile {
|
|
11
|
+
private readonly channel;
|
|
12
|
+
private readonly path;
|
|
13
|
+
/**
|
|
14
|
+
* @param storefront the storefront directory.
|
|
15
|
+
* @param channel the digest naming the channel this run syncs against.
|
|
16
|
+
*/
|
|
17
|
+
constructor(storefront: string, channel: string);
|
|
18
|
+
/** This channel's last sync — null before the first, or after a sync against another channel. */
|
|
19
|
+
read(): Promise<SyncRecord | null>;
|
|
20
|
+
/** Record a sync that left the laptop on `servingSha`, with the `.env` lines as `env` digests them. */
|
|
21
|
+
write(servingSha: string, env: Readonly<Record<string, string>>): Promise<void>;
|
|
22
|
+
/** The record's fields, checked one by one: a file sync cannot read is an error naming it. */
|
|
23
|
+
private parse;
|
|
24
|
+
private parseEnv;
|
|
25
|
+
private unreadableMessage;
|
|
26
|
+
/** The record's text — null before the first sync; any other failure propagates. */
|
|
27
|
+
private readIfPresent;
|
|
28
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { isJsonObject } from '../json.js';
|
|
4
|
+
import { SYNC_STATE_PATH } from './sync-repository.js';
|
|
5
|
+
/** The record's file, in the private state directory. */
|
|
6
|
+
const RECORD_FILE = 'state.json';
|
|
7
|
+
/** A commit id as git names one: lowercase hex digits — 40 of them (sha1) or 64 (sha256). */
|
|
8
|
+
const COMMIT_ID = /^[0-9a-f]+$/;
|
|
9
|
+
const COMMIT_ID_LENGTHS = new Set([40, 64]);
|
|
10
|
+
/**
|
|
11
|
+
* The last sync of one storefront (#2781), kept in `.forgecart/sync/state.json`
|
|
12
|
+
* beside the private repository: the channel it ran against — a digest of the
|
|
13
|
+
* admin API and the channel token, never the token — the serving commit the
|
|
14
|
+
* laptop's tree last matched, and a digest of every merchant `.env` line as it
|
|
15
|
+
* last travelled. A record of another channel is no record: a storefront pointed
|
|
16
|
+
* at another channel syncs as a first sync.
|
|
17
|
+
*/
|
|
18
|
+
export class SyncRecordFile {
|
|
19
|
+
channel;
|
|
20
|
+
path;
|
|
21
|
+
/**
|
|
22
|
+
* @param storefront the storefront directory.
|
|
23
|
+
* @param channel the digest naming the channel this run syncs against.
|
|
24
|
+
*/
|
|
25
|
+
constructor(storefront, channel) {
|
|
26
|
+
this.channel = channel;
|
|
27
|
+
this.path = join(storefront, ...SYNC_STATE_PATH, RECORD_FILE);
|
|
28
|
+
}
|
|
29
|
+
/** This channel's last sync — null before the first, or after a sync against another channel. */
|
|
30
|
+
async read() {
|
|
31
|
+
const raw = await this.readIfPresent();
|
|
32
|
+
if (raw === null)
|
|
33
|
+
return null;
|
|
34
|
+
const record = this.parse(raw);
|
|
35
|
+
return record.channel === this.channel ? record : null;
|
|
36
|
+
}
|
|
37
|
+
/** Record a sync that left the laptop on `servingSha`, with the `.env` lines as `env` digests them. */
|
|
38
|
+
async write(servingSha, env) {
|
|
39
|
+
const record = { channel: this.channel, servingSha, env: { ...env } };
|
|
40
|
+
await writeFile(this.path, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
/** The record's fields, checked one by one: a file sync cannot read is an error naming it. */
|
|
43
|
+
parse(raw) {
|
|
44
|
+
let parsed;
|
|
45
|
+
try {
|
|
46
|
+
parsed = JSON.parse(raw);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
throw new Error(this.unreadableMessage(), { cause: error });
|
|
50
|
+
}
|
|
51
|
+
if (!isJsonObject(parsed))
|
|
52
|
+
throw new Error(this.unreadableMessage());
|
|
53
|
+
const { channel, servingSha, env } = parsed;
|
|
54
|
+
if (typeof channel !== 'string' || typeof servingSha !== 'string') {
|
|
55
|
+
throw new Error(this.unreadableMessage());
|
|
56
|
+
}
|
|
57
|
+
if (!COMMIT_ID_LENGTHS.has(servingSha.length) || !COMMIT_ID.test(servingSha)) {
|
|
58
|
+
throw new Error(this.unreadableMessage());
|
|
59
|
+
}
|
|
60
|
+
return { channel, servingSha, env: this.parseEnv(env) };
|
|
61
|
+
}
|
|
62
|
+
parseEnv(env) {
|
|
63
|
+
if (!isJsonObject(env))
|
|
64
|
+
throw new Error(this.unreadableMessage());
|
|
65
|
+
const entries = Object.entries(env);
|
|
66
|
+
if (!entries.every((entry) => typeof entry[1] === 'string')) {
|
|
67
|
+
throw new Error(this.unreadableMessage());
|
|
68
|
+
}
|
|
69
|
+
return Object.fromEntries(entries);
|
|
70
|
+
}
|
|
71
|
+
unreadableMessage() {
|
|
72
|
+
return `${this.path} is not a sync record sync can read; remove it to sync as a first sync.`;
|
|
73
|
+
}
|
|
74
|
+
/** The record's text — null before the first sync; any other failure propagates. */
|
|
75
|
+
async readIfPresent() {
|
|
76
|
+
try {
|
|
77
|
+
return await readFile(this.path, 'utf8');
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code === 'ENOENT')
|
|
81
|
+
return null;
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=sync-record.file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-record.file.js","sourceRoot":"","sources":["../../../src/sync/sync-record.file.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type SyncMaterialized, type SyncTreeChange } from './type/sync.types.js';
|
|
2
|
+
/** Where sync keeps its private state, under the storefront's `.forgecart/` — which never travels. */
|
|
3
|
+
export declare const SYNC_STATE_PATH: readonly [".forgecart", "sync"];
|
|
4
|
+
/**
|
|
5
|
+
* The laptop's side of `forgecart sync` (#2781): a private bare git repository
|
|
6
|
+
* under `.forgecart/sync/`, its work tree the storefront. It snapshots the
|
|
7
|
+
* laptop's tree into its own index — the paths the board would carry, nothing
|
|
8
|
+
* the runtime keeps out of serving history — holds the serving commits the
|
|
9
|
+
* pulled bundles bring, writes a serving tree onto the laptop, and cuts the ONE
|
|
10
|
+
* commit a push sends as a git bundle on the serving tip.
|
|
11
|
+
*
|
|
12
|
+
* Every git run reads no system and no global config, and no `GIT_*` variable
|
|
13
|
+
* of the caller's: what travels never depends on the machine it runs on. The
|
|
14
|
+
* merchant's own repository, if the storefront is one, is never read or
|
|
15
|
+
* written.
|
|
16
|
+
*/
|
|
17
|
+
export declare class SyncRepository {
|
|
18
|
+
private readonly storefront;
|
|
19
|
+
private readonly stateDir;
|
|
20
|
+
private readonly gitDir;
|
|
21
|
+
constructor(storefront: string);
|
|
22
|
+
/**
|
|
23
|
+
* `git` must be on the PATH: sync keeps the laptop's tree in a git repository
|
|
24
|
+
* and moves it as git bundles. Refused by name, on exit 1.
|
|
25
|
+
*/
|
|
26
|
+
static assertGitOnPath(): Promise<void>;
|
|
27
|
+
/**
|
|
28
|
+
* Create the private repository on first use, and (re)write the paths that
|
|
29
|
+
* never travel. `git init --bare` refuses a work tree, so it alone runs
|
|
30
|
+
* without the repository's own environment.
|
|
31
|
+
*/
|
|
32
|
+
prepare(): Promise<void>;
|
|
33
|
+
/** The laptop's tree as it stands — every path that travels — written into the private index. */
|
|
34
|
+
snapshot(): Promise<string>;
|
|
35
|
+
/** Whether the private repository holds `sha` as a commit. */
|
|
36
|
+
hasCommit(sha: string): Promise<boolean>;
|
|
37
|
+
/** The tree with no entry — the base of a first sync. */
|
|
38
|
+
emptyTree(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Take a pulled bundle's serving branch into the private repository — git's
|
|
41
|
+
* own fetch from the bundle file, which checks every object it admits — and
|
|
42
|
+
* answer the serving commit it carried. The bundle file lives only as long
|
|
43
|
+
* as the fetch.
|
|
44
|
+
*/
|
|
45
|
+
fetchBundle(bytes: Buffer): Promise<string>;
|
|
46
|
+
/** Keep `sha` — the recorded base — reachable whatever the serving branch does next. */
|
|
47
|
+
keepBase(sha: string): Promise<void>;
|
|
48
|
+
/** The tree of `commit` that travels: its own, without the platform's `.forgecart/`. */
|
|
49
|
+
travellingTree(commit: string): Promise<string>;
|
|
50
|
+
/** The paths `to` changes against `from`, path by path. */
|
|
51
|
+
changes(from: string, to: string): Promise<SyncTreeChange[]>;
|
|
52
|
+
/**
|
|
53
|
+
* Write the tree `to` onto the laptop, standing at `from`: every path `from`
|
|
54
|
+
* holds and `to` does not is removed, every path `to` adds or changes is
|
|
55
|
+
* written, and nothing else on the laptop — no ignored file, nothing that
|
|
56
|
+
* never travels — is touched. The private index then holds `to`, so every
|
|
57
|
+
* path of it stays tracked whatever the laptop's ignore rules say.
|
|
58
|
+
*/
|
|
59
|
+
materialize(from: string, to: string): Promise<SyncMaterialized>;
|
|
60
|
+
/**
|
|
61
|
+
* The ONE commit a push sends: the laptop's tree on the serving tip `serving`,
|
|
62
|
+
* keeping the platform's `.forgecart/` the serving tree carries — the laptop's
|
|
63
|
+
* own never travels.
|
|
64
|
+
*/
|
|
65
|
+
pushCommit(localTree: string, serving: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* `commit` as the git bundle a push sends: its one ref `refs/heads/serving`,
|
|
68
|
+
* its one prerequisite the serving tip `serving` it was cut on.
|
|
69
|
+
*/
|
|
70
|
+
pushBundle(commit: string, serving: string): Promise<Buffer>;
|
|
71
|
+
/** The top-level entries of `tree`, in `ls-tree -z` form — the form `mktree -z` reads back. */
|
|
72
|
+
private topLevelEntries;
|
|
73
|
+
private makeTree;
|
|
74
|
+
/** An `ls-tree` entry's name: everything after its tab. */
|
|
75
|
+
private entryName;
|
|
76
|
+
/** A `diff-tree -z` record's status letter — the last word of its metadata. */
|
|
77
|
+
private changeKind;
|
|
78
|
+
/** Write `paths` of `tree` onto the laptop, replacing what stands there — through an index of its own. */
|
|
79
|
+
private checkOut;
|
|
80
|
+
/** Remove one file the laptop holds, and every directory the removal leaves empty. */
|
|
81
|
+
private removePath;
|
|
82
|
+
/** A directory's entries — null when it is gone. */
|
|
83
|
+
private entriesOf;
|
|
84
|
+
private exists;
|
|
85
|
+
/** A fresh bundle path in the private state directory, alive for one transfer. */
|
|
86
|
+
private spoolPath;
|
|
87
|
+
private excludeFile;
|
|
88
|
+
/** A one-line answer — an object id — without its newline. */
|
|
89
|
+
private line;
|
|
90
|
+
/**
|
|
91
|
+
* Run git on the private repository, the storefront its work tree. A non-zero
|
|
92
|
+
* exit is an error naming the command and git's own words — unless the
|
|
93
|
+
* caller reads it as an answer.
|
|
94
|
+
*/
|
|
95
|
+
private git;
|
|
96
|
+
/** A git run that exited non-zero is an error naming the command and git's own words. */
|
|
97
|
+
private assertSucceeded;
|
|
98
|
+
private spawnGit;
|
|
99
|
+
/**
|
|
100
|
+
* The flags every run carries: byte-exact content, the work tree the bare
|
|
101
|
+
* repository is given, the paths that never travel as the only excludes
|
|
102
|
+
* besides the storefront's own `.gitignore`, and an identity for the carrier
|
|
103
|
+
* commit.
|
|
104
|
+
*/
|
|
105
|
+
private gitFlags;
|
|
106
|
+
/** The repository's environment: the private repository, the storefront as its work tree, and `indexFile`. */
|
|
107
|
+
private repositoryEnv;
|
|
108
|
+
/** The caller's environment without any `GIT_*` variable, reading no system and no global config. */
|
|
109
|
+
private isolatedEnv;
|
|
110
|
+
}
|