@forgecart/cli 2.202610130131.0 → 2.202610130431.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/admin-api/admin-api-refusal.error.d.ts +2 -2
- package/dist/src/admin-api/admin-api-refusal.error.js +2 -2
- package/dist/src/admin-api/admin-api.client.d.ts +8 -4
- package/dist/src/admin-api/admin-api.client.js +11 -9
- package/dist/src/admin-api/admin-api.client.js.map +1 -1
- package/dist/src/admin-api/admin-api.result.d.ts +7 -0
- package/dist/src/admin-api/admin-api.result.js +31 -2
- package/dist/src/admin-api/admin-api.result.js.map +1 -1
- package/dist/src/authorization/cli-authorization-flow.d.ts +21 -14
- package/dist/src/authorization/cli-authorization-flow.js +40 -31
- package/dist/src/authorization/cli-authorization-flow.js.map +1 -1
- package/dist/src/authorization/cli-credential-check.d.ts +6 -13
- package/dist/src/authorization/cli-credential-check.js +6 -23
- package/dist/src/authorization/cli-credential-check.js.map +1 -1
- package/dist/src/authorization/cli-credential-step.d.ts +74 -0
- package/dist/src/authorization/cli-credential-step.js +154 -0
- package/dist/src/authorization/cli-credential-step.js.map +1 -0
- package/dist/src/authorization/cli-unauthorized-refusal.d.ts +18 -8
- package/dist/src/authorization/cli-unauthorized-refusal.js +26 -11
- package/dist/src/authorization/cli-unauthorized-refusal.js.map +1 -1
- package/dist/src/authorization/type/cli-authorization.types.d.ts +36 -4
- package/dist/src/authorization/type/cli-authorization.types.js +16 -3
- package/dist/src/authorization/type/cli-authorization.types.js.map +1 -1
- package/dist/src/commands/init.d.ts +0 -10
- package/dist/src/commands/init.js +17 -42
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/sync.js +2 -2
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/deploy/deploy.runner.d.ts +15 -8
- package/dist/src/deploy/deploy.runner.js +19 -10
- package/dist/src/deploy/deploy.runner.js.map +1 -1
- package/dist/src/sync/sync-bundle-push.d.ts +34 -0
- package/dist/src/sync/sync-bundle-push.js +97 -0
- package/dist/src/sync/sync-bundle-push.js.map +1 -0
- package/dist/src/sync/sync-dotenv.file.d.ts +30 -22
- package/dist/src/sync/sync-dotenv.file.js +49 -54
- package/dist/src/sync/sync-dotenv.file.js.map +1 -1
- package/dist/src/sync/sync-env-channel.d.ts +47 -0
- package/dist/src/sync/sync-env-channel.js +106 -0
- package/dist/src/sync/sync-env-channel.js.map +1 -0
- package/dist/src/sync/sync-env.runner.d.ts +26 -10
- package/dist/src/sync/sync-env.runner.js +73 -40
- package/dist/src/sync/sync-env.runner.js.map +1 -1
- package/dist/src/sync/sync-record.file.d.ts +18 -10
- package/dist/src/sync/sync-record.file.js +30 -10
- package/dist/src/sync/sync-record.file.js.map +1 -1
- package/dist/src/sync/sync-repository.d.ts +7 -2
- package/dist/src/sync/sync-repository.js +26 -5
- package/dist/src/sync/sync-repository.js.map +1 -1
- package/dist/src/sync/sync-round.d.ts +28 -15
- package/dist/src/sync/sync-round.js +78 -39
- package/dist/src/sync/sync-round.js.map +1 -1
- package/dist/src/sync/sync-watch.d.ts +2 -1
- package/dist/src/sync/sync-watch.js +2 -1
- package/dist/src/sync/sync-watch.js.map +1 -1
- package/dist/src/sync/sync.runner.d.ts +16 -8
- package/dist/src/sync/sync.runner.js +30 -26
- package/dist/src/sync/sync.runner.js.map +1 -1
- package/dist/src/sync/type/sync.types.d.ts +78 -16
- package/dist/src/sync/type/sync.types.js.map +1 -1
- package/dist/src/sync/workspace-sync.client.d.ts +43 -16
- package/dist/src/sync/workspace-sync.client.js +125 -31
- package/dist/src/sync/workspace-sync.client.js.map +1 -1
- package/dist/src/util/dotenv.util.d.ts +19 -5
- package/dist/src/util/dotenv.util.js +42 -5
- package/dist/src/util/dotenv.util.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type CliCredentials } from '../authorization/type/cli-authorization.types.js';
|
|
2
|
+
import type { SyncEnvDigests, SyncEnvFileLines, SyncOutgoingEnv, SyncTakenEnv } from './type/sync.types.js';
|
|
3
|
+
/**
|
|
4
|
+
* The laptop's side of `forgecart sync`'s env channel (#2781): EVERY env file a
|
|
5
|
+
* storefront reads — `.env`, `.env.local`, `.env.development`,
|
|
6
|
+
* `.env.development.local`, `.env.production`, `.env.production.local` — carried
|
|
7
|
+
* file for file, each one's merchant lines into the other side's file of the
|
|
8
|
+
* same name, so every key resolves in the workspace as it does on the laptop.
|
|
9
|
+
* The files are never flattened into one, and the platform's `FORGECART_*`
|
|
10
|
+
* keys never cross in any of them. What travelled is recorded per file and per
|
|
11
|
+
* key. A pull also writes the run's four credentials into `.env` — the `.env` a
|
|
12
|
+
* fresh pull writes.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SyncEnvChannel {
|
|
15
|
+
private readonly files;
|
|
16
|
+
/** @param storefront the storefront directory. */
|
|
17
|
+
constructor(storefront: string);
|
|
18
|
+
/**
|
|
19
|
+
* The merchant lines a push carries — one entry per env file the laptop
|
|
20
|
+
* holds, its lines changed since `recorded` — and the digests to record once
|
|
21
|
+
* they land.
|
|
22
|
+
*/
|
|
23
|
+
outgoing(recorded: SyncEnvDigests | null): Promise<SyncOutgoingEnv>;
|
|
24
|
+
/**
|
|
25
|
+
* The workspace's merchant lines a pull would take — one entry per env file
|
|
26
|
+
* the workspace holds, its lines for every key the laptop has not changed
|
|
27
|
+
* since `recorded`.
|
|
28
|
+
*/
|
|
29
|
+
incoming(workspaceFiles: readonly SyncEnvFileLines[], recorded: SyncEnvDigests | null): Promise<SyncEnvFileLines[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Take each workspace env file's merchant lines into the laptop file of the
|
|
32
|
+
* same name — created when the laptop lacks it — and the four credentials
|
|
33
|
+
* into `.env`. A file the workspace does not hold is left as it is, with the
|
|
34
|
+
* digests it last travelled with.
|
|
35
|
+
*/
|
|
36
|
+
take(workspaceFiles: readonly SyncEnvFileLines[], credentials: CliCredentials, recorded: SyncEnvDigests | null): Promise<SyncTakenEnv>;
|
|
37
|
+
/** Take into one file — null for a file with neither workspace lines nor credentials to take. */
|
|
38
|
+
private takeInto;
|
|
39
|
+
/**
|
|
40
|
+
* The platform keys a pull writes into `file`: the four credentials in
|
|
41
|
+
* `.env`, in the order init writes them; none elsewhere.
|
|
42
|
+
*/
|
|
43
|
+
private platformEntries;
|
|
44
|
+
/** The digests `file` last travelled with: null before a first sync, none for a file that never travelled. */
|
|
45
|
+
private recordOf;
|
|
46
|
+
private linesByFile;
|
|
47
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { CliCredentialKey, } from '../authorization/type/cli-authorization.types.js';
|
|
2
|
+
import { STOREFRONT_ENV_FILES, StorefrontEnvFile } from '../util/dotenv.util.js';
|
|
3
|
+
import { SyncDotenvFile } from './sync-dotenv.file.js';
|
|
4
|
+
/**
|
|
5
|
+
* The laptop's side of `forgecart sync`'s env channel (#2781): EVERY env file a
|
|
6
|
+
* storefront reads — `.env`, `.env.local`, `.env.development`,
|
|
7
|
+
* `.env.development.local`, `.env.production`, `.env.production.local` — carried
|
|
8
|
+
* file for file, each one's merchant lines into the other side's file of the
|
|
9
|
+
* same name, so every key resolves in the workspace as it does on the laptop.
|
|
10
|
+
* The files are never flattened into one, and the platform's `FORGECART_*`
|
|
11
|
+
* keys never cross in any of them. What travelled is recorded per file and per
|
|
12
|
+
* key. A pull also writes the run's four credentials into `.env` — the `.env` a
|
|
13
|
+
* fresh pull writes.
|
|
14
|
+
*/
|
|
15
|
+
export class SyncEnvChannel {
|
|
16
|
+
files;
|
|
17
|
+
/** @param storefront the storefront directory. */
|
|
18
|
+
constructor(storefront) {
|
|
19
|
+
this.files = STOREFRONT_ENV_FILES.map((file) => new SyncDotenvFile(storefront, file));
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The merchant lines a push carries — one entry per env file the laptop
|
|
23
|
+
* holds, its lines changed since `recorded` — and the digests to record once
|
|
24
|
+
* they land.
|
|
25
|
+
*/
|
|
26
|
+
async outgoing(recorded) {
|
|
27
|
+
const files = [];
|
|
28
|
+
const digests = {};
|
|
29
|
+
for (const envFile of this.files) {
|
|
30
|
+
const outgoing = await envFile.outgoing(this.recordOf(recorded, envFile.file));
|
|
31
|
+
if (outgoing === null)
|
|
32
|
+
continue;
|
|
33
|
+
files.push({ file: envFile.file, lines: outgoing.lines });
|
|
34
|
+
digests[envFile.file] = outgoing.digests;
|
|
35
|
+
}
|
|
36
|
+
return { files, digests };
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The workspace's merchant lines a pull would take — one entry per env file
|
|
40
|
+
* the workspace holds, its lines for every key the laptop has not changed
|
|
41
|
+
* since `recorded`.
|
|
42
|
+
*/
|
|
43
|
+
async incoming(workspaceFiles, recorded) {
|
|
44
|
+
const workspace = this.linesByFile(workspaceFiles);
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const envFile of this.files) {
|
|
47
|
+
const workspaceLines = workspace.get(envFile.file);
|
|
48
|
+
if (workspaceLines === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
const lines = await envFile.incoming(workspaceLines, this.recordOf(recorded, envFile.file));
|
|
51
|
+
files.push({ file: envFile.file, lines });
|
|
52
|
+
}
|
|
53
|
+
return files;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Take each workspace env file's merchant lines into the laptop file of the
|
|
57
|
+
* same name — created when the laptop lacks it — and the four credentials
|
|
58
|
+
* into `.env`. A file the workspace does not hold is left as it is, with the
|
|
59
|
+
* digests it last travelled with.
|
|
60
|
+
*/
|
|
61
|
+
async take(workspaceFiles, credentials, recorded) {
|
|
62
|
+
const workspace = this.linesByFile(workspaceFiles);
|
|
63
|
+
const digests = {};
|
|
64
|
+
let taken = 0;
|
|
65
|
+
for (const envFile of this.files) {
|
|
66
|
+
const workspaceLines = workspace.get(envFile.file);
|
|
67
|
+
const result = await this.takeInto(envFile, workspaceLines, credentials, recorded);
|
|
68
|
+
const fileDigests = result?.digests ?? recorded?.[envFile.file];
|
|
69
|
+
if (fileDigests !== undefined)
|
|
70
|
+
digests[envFile.file] = fileDigests;
|
|
71
|
+
taken += result?.taken ?? 0;
|
|
72
|
+
}
|
|
73
|
+
return { taken, digests };
|
|
74
|
+
}
|
|
75
|
+
/** Take into one file — null for a file with neither workspace lines nor credentials to take. */
|
|
76
|
+
async takeInto(envFile, workspaceLines, credentials, recorded) {
|
|
77
|
+
const platform = this.platformEntries(envFile.file, credentials);
|
|
78
|
+
if (workspaceLines === undefined && platform.size === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return envFile.take(workspaceLines ?? [], platform, this.recordOf(recorded, envFile.file));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The platform keys a pull writes into `file`: the four credentials in
|
|
84
|
+
* `.env`, in the order init writes them; none elsewhere.
|
|
85
|
+
*/
|
|
86
|
+
platformEntries(file, credentials) {
|
|
87
|
+
if (file !== StorefrontEnvFile.BASE)
|
|
88
|
+
return new Map();
|
|
89
|
+
return new Map([
|
|
90
|
+
[CliCredentialKey.CHANNEL_TOKEN, credentials.channelToken],
|
|
91
|
+
[CliCredentialKey.SHOP_API_URL, credentials.shopApiUrl],
|
|
92
|
+
[CliCredentialKey.ADMIN_SECRET, credentials.adminSecret],
|
|
93
|
+
[CliCredentialKey.ADMIN_API_URL, credentials.adminApiUrl],
|
|
94
|
+
]);
|
|
95
|
+
}
|
|
96
|
+
/** The digests `file` last travelled with: null before a first sync, none for a file that never travelled. */
|
|
97
|
+
recordOf(recorded, file) {
|
|
98
|
+
if (recorded === null)
|
|
99
|
+
return null;
|
|
100
|
+
return recorded[file] ?? {};
|
|
101
|
+
}
|
|
102
|
+
linesByFile(files) {
|
|
103
|
+
return new Map(files.map(({ file, lines }) => [file, lines]));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=sync-env-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-env-channel.js","sourceRoot":"","sources":["../../../src/sync/sync-env-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { SyncEnvOptions } from './type/sync.types.js';
|
|
2
2
|
/**
|
|
3
3
|
* `forgecart sync-env [name]` (#2781) — the workspace's side of `forgecart
|
|
4
|
-
* sync`, which the admin API runs in the pod
|
|
5
|
-
* storefront
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* sync`, which the admin API runs in the pod. It reads EVERY env file the
|
|
5
|
+
* storefront holds — `.env`, `.env.local`, `.env.development`,
|
|
6
|
+
* `.env.development.local`, `.env.production`, `.env.production.local` — or,
|
|
7
|
+
* with `--merge`, merges the lines FORGECART_SYNC_ENV_FILES carries for each
|
|
8
|
+
* file into the storefront file of that name under init's merge rule, creating
|
|
9
|
+
* one the storefront lacks. The files stay apart, so the dev-server resolves
|
|
10
|
+
* every key as the laptop does. It prints ONE JSON report: one entry per env
|
|
11
|
+
* file the storefront holds — its merchant lines as they stand, and how many
|
|
12
|
+
* keys the merge carried in. The platform's own `FORGECART_*` keys are never
|
|
13
|
+
* read out and never written: a laptop's never land, the pod's never leave.
|
|
10
14
|
*/
|
|
11
15
|
export declare class SyncEnvRunner {
|
|
12
16
|
private readonly name;
|
|
@@ -17,13 +21,25 @@ export declare class SyncEnvRunner {
|
|
|
17
21
|
*/
|
|
18
22
|
constructor(name: string | undefined, options: SyncEnvOptions);
|
|
19
23
|
run(): Promise<void>;
|
|
20
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* One env file once the run is done: the merchant keys carried for it merged
|
|
26
|
+
* in — the file created when the storefront lacks it — and its merchant lines
|
|
27
|
+
* as they then stand; null for a file the storefront does not hold.
|
|
28
|
+
*/
|
|
29
|
+
private settle;
|
|
30
|
+
/** A file nothing was merged into, as it stands — null when the storefront does not hold it. */
|
|
31
|
+
private held;
|
|
32
|
+
/**
|
|
33
|
+
* The merchant keys a merge carries for each env file, keyed to their values
|
|
34
|
+
* — platform keys and other lines dropped; none for a read.
|
|
35
|
+
*/
|
|
21
36
|
private incoming;
|
|
22
|
-
private
|
|
37
|
+
private managedOf;
|
|
38
|
+
private parseEntries;
|
|
39
|
+
/** One entry: an env file the storefront reads — never a path outside them — and its lines. */
|
|
40
|
+
private parseEntry;
|
|
23
41
|
private malformedMessage;
|
|
24
42
|
private merchantLines;
|
|
25
43
|
/** The report the admin API parses: ONE line of JSON on stdout. */
|
|
26
44
|
private report;
|
|
27
|
-
/** The `.env` text — empty when the storefront has none; any other failure propagates. */
|
|
28
|
-
private read;
|
|
29
45
|
}
|
|
@@ -1,20 +1,25 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
2
|
import { join, resolve } from 'node:path';
|
|
3
|
+
import { isJsonObject } from '../json.js';
|
|
3
4
|
import { print } from '../output.js';
|
|
4
|
-
import { dotenvValue, mergeDotenv, merchantAssignments } from '../util/dotenv.util.js';
|
|
5
|
+
import { STOREFRONT_ENV_FILES, dotenvValue, isStorefrontEnvFile, mergeDotenv, merchantAssignments, readDotenvFile, } from '../util/dotenv.util.js';
|
|
5
6
|
/**
|
|
6
7
|
* The variable a merge's lines arrive in — the pod records a command's line,
|
|
7
8
|
* never its environment, so the lines never land in a record.
|
|
8
9
|
*/
|
|
9
|
-
const
|
|
10
|
+
const SYNC_ENV_FILES_VARIABLE = 'FORGECART_SYNC_ENV_FILES';
|
|
10
11
|
/**
|
|
11
12
|
* `forgecart sync-env [name]` (#2781) — the workspace's side of `forgecart
|
|
12
|
-
* sync`, which the admin API runs in the pod
|
|
13
|
-
* storefront
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* sync`, which the admin API runs in the pod. It reads EVERY env file the
|
|
14
|
+
* storefront holds — `.env`, `.env.local`, `.env.development`,
|
|
15
|
+
* `.env.development.local`, `.env.production`, `.env.production.local` — or,
|
|
16
|
+
* with `--merge`, merges the lines FORGECART_SYNC_ENV_FILES carries for each
|
|
17
|
+
* file into the storefront file of that name under init's merge rule, creating
|
|
18
|
+
* one the storefront lacks. The files stay apart, so the dev-server resolves
|
|
19
|
+
* every key as the laptop does. It prints ONE JSON report: one entry per env
|
|
20
|
+
* file the storefront holds — its merchant lines as they stand, and how many
|
|
21
|
+
* keys the merge carried in. The platform's own `FORGECART_*` keys are never
|
|
22
|
+
* read out and never written: a laptop's never land, the pod's never leave.
|
|
18
23
|
*/
|
|
19
24
|
export class SyncEnvRunner {
|
|
20
25
|
name;
|
|
@@ -28,31 +33,60 @@ export class SyncEnvRunner {
|
|
|
28
33
|
this.options = options;
|
|
29
34
|
}
|
|
30
35
|
async run() {
|
|
31
|
-
const
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const storefront = this.name ? resolve(process.cwd(), this.name) : process.cwd();
|
|
37
|
+
const incoming = this.incoming();
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const file of STOREFRONT_ENV_FILES) {
|
|
40
|
+
const settled = await this.settle(storefront, file, incoming.get(file));
|
|
41
|
+
if (settled !== null)
|
|
42
|
+
files.push(settled);
|
|
36
43
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
this.report({ files });
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* One env file once the run is done: the merchant keys carried for it merged
|
|
48
|
+
* in — the file created when the storefront lacks it — and its merchant lines
|
|
49
|
+
* as they then stand; null for a file the storefront does not hold.
|
|
50
|
+
*/
|
|
51
|
+
async settle(storefront, file, managed) {
|
|
52
|
+
const path = join(storefront, file);
|
|
53
|
+
const existing = await readDotenvFile(path);
|
|
54
|
+
if (managed === undefined || managed.size === 0)
|
|
55
|
+
return this.held(file, existing);
|
|
56
|
+
const next = mergeDotenv(existing ?? '', managed);
|
|
43
57
|
if (next !== existing)
|
|
44
|
-
await writeFile(
|
|
45
|
-
|
|
58
|
+
await writeFile(path, next, 'utf8');
|
|
59
|
+
return { file, lines: this.merchantLines(next), merged: managed.size };
|
|
60
|
+
}
|
|
61
|
+
/** A file nothing was merged into, as it stands — null when the storefront does not hold it. */
|
|
62
|
+
held(file, content) {
|
|
63
|
+
if (content === null)
|
|
64
|
+
return null;
|
|
65
|
+
return { file, lines: this.merchantLines(content), merged: 0 };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* The merchant keys a merge carries for each env file, keyed to their values
|
|
69
|
+
* — platform keys and other lines dropped; none for a read.
|
|
70
|
+
*/
|
|
71
|
+
incoming() {
|
|
72
|
+
const incoming = new Map();
|
|
73
|
+
if (this.options.merge !== true)
|
|
74
|
+
return incoming;
|
|
75
|
+
for (const entry of this.parseEntries(process.env[SYNC_ENV_FILES_VARIABLE])) {
|
|
76
|
+
const { file, lines } = this.parseEntry(entry);
|
|
77
|
+
if (incoming.has(file))
|
|
78
|
+
throw new Error(this.malformedMessage());
|
|
79
|
+
incoming.set(file, this.managedOf(lines));
|
|
80
|
+
}
|
|
81
|
+
return incoming;
|
|
46
82
|
}
|
|
47
|
-
|
|
48
|
-
incoming(raw) {
|
|
49
|
-
const lines = this.parseLines(raw);
|
|
83
|
+
managedOf(lines) {
|
|
50
84
|
return new Map([...merchantAssignments(lines)].map(([key, line]) => [
|
|
51
85
|
key,
|
|
52
86
|
dotenvValue(line, key),
|
|
53
87
|
]));
|
|
54
88
|
}
|
|
55
|
-
|
|
89
|
+
parseEntries(raw) {
|
|
56
90
|
if (raw === undefined)
|
|
57
91
|
throw new Error(this.malformedMessage());
|
|
58
92
|
let parsed;
|
|
@@ -64,14 +98,24 @@ export class SyncEnvRunner {
|
|
|
64
98
|
}
|
|
65
99
|
if (!Array.isArray(parsed))
|
|
66
100
|
throw new Error(this.malformedMessage());
|
|
67
|
-
|
|
101
|
+
return parsed;
|
|
102
|
+
}
|
|
103
|
+
/** One entry: an env file the storefront reads — never a path outside them — and its lines. */
|
|
104
|
+
parseEntry(entry) {
|
|
105
|
+
if (!isJsonObject(entry))
|
|
106
|
+
throw new Error(this.malformedMessage());
|
|
107
|
+
const { file, lines } = entry;
|
|
108
|
+
if (typeof file !== 'string' || !isStorefrontEnvFile(file) || !Array.isArray(lines)) {
|
|
109
|
+
throw new Error(this.malformedMessage());
|
|
110
|
+
}
|
|
111
|
+
const items = lines;
|
|
68
112
|
if (!items.every((item) => typeof item === 'string')) {
|
|
69
113
|
throw new Error(this.malformedMessage());
|
|
70
114
|
}
|
|
71
|
-
return items;
|
|
115
|
+
return { file, lines: items };
|
|
72
116
|
}
|
|
73
117
|
malformedMessage() {
|
|
74
|
-
return `--merge reads ${
|
|
118
|
+
return `--merge reads ${SYNC_ENV_FILES_VARIABLE}, which must hold a JSON list of { "file", "lines" } entries — each file one of ${STOREFRONT_ENV_FILES.join(', ')}, named once, and its lines KEY=value lines.`;
|
|
75
119
|
}
|
|
76
120
|
merchantLines(content) {
|
|
77
121
|
return [...merchantAssignments(content.split('\n')).values()];
|
|
@@ -80,16 +124,5 @@ export class SyncEnvRunner {
|
|
|
80
124
|
report(report) {
|
|
81
125
|
print(JSON.stringify(report));
|
|
82
126
|
}
|
|
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
127
|
}
|
|
95
128
|
//# sourceMappingURL=sync-env.runner.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-env.runner.js","sourceRoot":"","sources":["../../../src/sync/sync-env.runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"sync-env.runner.js","sourceRoot":"","sources":["../../../src/sync/sync-env.runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,27 +1,35 @@
|
|
|
1
|
-
import type { SyncRecord } from './type/sync.types.js';
|
|
1
|
+
import type { SyncEnvDigests, SyncRecord } from './type/sync.types.js';
|
|
2
2
|
/**
|
|
3
3
|
* The last sync of one storefront (#2781), kept in `.forgecart/sync/state.json`
|
|
4
4
|
* beside the private repository: the channel it ran against — a digest of the
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* at another channel syncs as a first sync.
|
|
5
|
+
* channel token, never the token — the serving commit the laptop's tree last
|
|
6
|
+
* matched, and a digest of every merchant line of every env file as it last
|
|
7
|
+
* travelled, file by file. A record of another channel is no record: a
|
|
8
|
+
* storefront pointed at another channel syncs as a first sync.
|
|
9
|
+
*
|
|
10
|
+
* The admin API's URL is no part of the channel: it is the route a run reaches
|
|
11
|
+
* the shop by, and a hop in front of the shop — a proxy, an ingress, the edge —
|
|
12
|
+
* changes it while the channel and its board stay the same (factory task
|
|
13
|
+
* 2375). A laptop that pulled through one route pushes through another onto
|
|
14
|
+
* the base it pulled.
|
|
9
15
|
*/
|
|
10
16
|
export declare class SyncRecordFile {
|
|
11
|
-
private readonly channel;
|
|
12
17
|
private readonly path;
|
|
18
|
+
private readonly channel;
|
|
13
19
|
/**
|
|
14
20
|
* @param storefront the storefront directory.
|
|
15
|
-
* @param
|
|
21
|
+
* @param channelToken the token of the channel this run syncs against, kept only as its digest.
|
|
16
22
|
*/
|
|
17
|
-
constructor(storefront: string,
|
|
23
|
+
constructor(storefront: string, channelToken: string);
|
|
18
24
|
/** This channel's last sync — null before the first, or after a sync against another channel. */
|
|
19
25
|
read(): Promise<SyncRecord | null>;
|
|
20
|
-
/** Record a sync that left the laptop on `servingSha`, with the
|
|
21
|
-
write(servingSha: string, env: Readonly<
|
|
26
|
+
/** Record a sync that left the laptop on `servingSha`, with the env files' lines as `env` digests them. */
|
|
27
|
+
write(servingSha: string, env: Readonly<SyncEnvDigests>): Promise<void>;
|
|
22
28
|
/** The record's fields, checked one by one: a file sync cannot read is an error naming it. */
|
|
23
29
|
private parse;
|
|
30
|
+
/** The env digests, file by file: every file one a storefront reads, every digest a string. */
|
|
24
31
|
private parseEnv;
|
|
32
|
+
private parseFileDigests;
|
|
25
33
|
private unreadableMessage;
|
|
26
34
|
/** The record's text — null before the first sync; any other failure propagates. */
|
|
27
35
|
private readIfPresent;
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { isJsonObject } from '../json.js';
|
|
5
|
+
import { isStorefrontEnvFile } from '../util/dotenv.util.js';
|
|
4
6
|
import { SYNC_STATE_PATH } from './sync-repository.js';
|
|
5
7
|
/** The record's file, in the private state directory. */
|
|
6
8
|
const RECORD_FILE = 'state.json';
|
|
@@ -10,21 +12,27 @@ const COMMIT_ID_LENGTHS = new Set([40, 64]);
|
|
|
10
12
|
/**
|
|
11
13
|
* The last sync of one storefront (#2781), kept in `.forgecart/sync/state.json`
|
|
12
14
|
* beside the private repository: the channel it ran against — a digest of the
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* at another channel syncs as a first sync.
|
|
15
|
+
* channel token, never the token — the serving commit the laptop's tree last
|
|
16
|
+
* matched, and a digest of every merchant line of every env file as it last
|
|
17
|
+
* travelled, file by file. A record of another channel is no record: a
|
|
18
|
+
* storefront pointed at another channel syncs as a first sync.
|
|
19
|
+
*
|
|
20
|
+
* The admin API's URL is no part of the channel: it is the route a run reaches
|
|
21
|
+
* the shop by, and a hop in front of the shop — a proxy, an ingress, the edge —
|
|
22
|
+
* changes it while the channel and its board stay the same (factory task
|
|
23
|
+
* 2375). A laptop that pulled through one route pushes through another onto
|
|
24
|
+
* the base it pulled.
|
|
17
25
|
*/
|
|
18
26
|
export class SyncRecordFile {
|
|
19
|
-
channel;
|
|
20
27
|
path;
|
|
28
|
+
channel;
|
|
21
29
|
/**
|
|
22
30
|
* @param storefront the storefront directory.
|
|
23
|
-
* @param
|
|
31
|
+
* @param channelToken the token of the channel this run syncs against, kept only as its digest.
|
|
24
32
|
*/
|
|
25
|
-
constructor(storefront,
|
|
26
|
-
this.channel = channel;
|
|
33
|
+
constructor(storefront, channelToken) {
|
|
27
34
|
this.path = join(storefront, ...SYNC_STATE_PATH, RECORD_FILE);
|
|
35
|
+
this.channel = createHash('sha256').update(channelToken).digest('hex');
|
|
28
36
|
}
|
|
29
37
|
/** This channel's last sync — null before the first, or after a sync against another channel. */
|
|
30
38
|
async read() {
|
|
@@ -34,7 +42,7 @@ export class SyncRecordFile {
|
|
|
34
42
|
const record = this.parse(raw);
|
|
35
43
|
return record.channel === this.channel ? record : null;
|
|
36
44
|
}
|
|
37
|
-
/** Record a sync that left the laptop on `servingSha`, with the
|
|
45
|
+
/** Record a sync that left the laptop on `servingSha`, with the env files' lines as `env` digests them. */
|
|
38
46
|
async write(servingSha, env) {
|
|
39
47
|
const record = { channel: this.channel, servingSha, env: { ...env } };
|
|
40
48
|
await writeFile(this.path, `${JSON.stringify(record, null, 2)}\n`, 'utf8');
|
|
@@ -59,10 +67,22 @@ export class SyncRecordFile {
|
|
|
59
67
|
}
|
|
60
68
|
return { channel, servingSha, env: this.parseEnv(env) };
|
|
61
69
|
}
|
|
70
|
+
/** The env digests, file by file: every file one a storefront reads, every digest a string. */
|
|
62
71
|
parseEnv(env) {
|
|
63
72
|
if (!isJsonObject(env))
|
|
64
73
|
throw new Error(this.unreadableMessage());
|
|
65
|
-
const
|
|
74
|
+
const digests = {};
|
|
75
|
+
for (const [file, fileDigests] of Object.entries(env)) {
|
|
76
|
+
if (!isStorefrontEnvFile(file))
|
|
77
|
+
throw new Error(this.unreadableMessage());
|
|
78
|
+
digests[file] = this.parseFileDigests(fileDigests);
|
|
79
|
+
}
|
|
80
|
+
return digests;
|
|
81
|
+
}
|
|
82
|
+
parseFileDigests(fileDigests) {
|
|
83
|
+
if (!isJsonObject(fileDigests))
|
|
84
|
+
throw new Error(this.unreadableMessage());
|
|
85
|
+
const entries = Object.entries(fileDigests);
|
|
66
86
|
if (!entries.every((entry) => typeof entry[1] === 'string')) {
|
|
67
87
|
throw new Error(this.unreadableMessage());
|
|
68
88
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-record.file.js","sourceRoot":"","sources":["../../../src/sync/sync-record.file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"sync-record.file.js","sourceRoot":"","sources":["../../../src/sync/sync-record.file.ts"],"names":[],"mappings":"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"}
|
|
@@ -60,12 +60,17 @@ export declare class SyncRepository {
|
|
|
60
60
|
/**
|
|
61
61
|
* The ONE commit a push sends: the laptop's tree on the serving tip `serving`,
|
|
62
62
|
* keeping the platform's `.forgecart/` the serving tree carries — the laptop's
|
|
63
|
-
* own never travels.
|
|
63
|
+
* own never travels. It is dated as the serving tip it is cut on, so the same
|
|
64
|
+
* tree cut on the same tip is the same commit on every run: a run that
|
|
65
|
+
* re-sends a push an earlier run broke off sends the very bundle the
|
|
66
|
+
* workspace holds parts of. The pod mints the landed commit itself.
|
|
64
67
|
*/
|
|
65
68
|
pushCommit(localTree: string, serving: string): Promise<string>;
|
|
66
69
|
/**
|
|
67
70
|
* `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.
|
|
71
|
+
* its one prerequisite the serving tip `serving` it was cut on. The pack is
|
|
72
|
+
* written by one thread, so the same commit is the same bundle, byte for
|
|
73
|
+
* byte, on every run.
|
|
69
74
|
*/
|
|
70
75
|
pushBundle(commit: string, serving: string): Promise<Buffer>;
|
|
71
76
|
/** The top-level entries of `tree`, in `ls-tree -z` form — the form `mktree -z` reads back. */
|
|
@@ -188,23 +188,41 @@ export class SyncRepository {
|
|
|
188
188
|
/**
|
|
189
189
|
* The ONE commit a push sends: the laptop's tree on the serving tip `serving`,
|
|
190
190
|
* keeping the platform's `.forgecart/` the serving tree carries — the laptop's
|
|
191
|
-
* own never travels.
|
|
191
|
+
* own never travels. It is dated as the serving tip it is cut on, so the same
|
|
192
|
+
* tree cut on the same tip is the same commit on every run: a run that
|
|
193
|
+
* re-sends a push an earlier run broke off sends the very bundle the
|
|
194
|
+
* workspace holds parts of. The pod mints the landed commit itself.
|
|
192
195
|
*/
|
|
193
196
|
async pushCommit(localTree, serving) {
|
|
194
197
|
const local = await this.topLevelEntries(localTree);
|
|
195
198
|
const platform = (await this.topLevelEntries(`${serving}^{tree}`)).filter((entry) => this.entryName(entry) === PLATFORM_DIR);
|
|
196
199
|
const tree = await this.makeTree([...local, ...platform]);
|
|
197
|
-
|
|
200
|
+
const servingDate = await this.git(['log', '-1', '--format=%cd', '--date=raw', serving]);
|
|
201
|
+
const date = `@${this.line(servingDate)}`;
|
|
202
|
+
const run = await this.git(['commit-tree', tree, '-p', serving, '-m', PUSH_MESSAGE], {
|
|
203
|
+
env: { GIT_AUTHOR_DATE: date, GIT_COMMITTER_DATE: date },
|
|
204
|
+
});
|
|
205
|
+
return this.line(run);
|
|
198
206
|
}
|
|
199
207
|
/**
|
|
200
208
|
* `commit` as the git bundle a push sends: its one ref `refs/heads/serving`,
|
|
201
|
-
* its one prerequisite the serving tip `serving` it was cut on.
|
|
209
|
+
* its one prerequisite the serving tip `serving` it was cut on. The pack is
|
|
210
|
+
* written by one thread, so the same commit is the same bundle, byte for
|
|
211
|
+
* byte, on every run.
|
|
202
212
|
*/
|
|
203
213
|
async pushBundle(commit, serving) {
|
|
204
214
|
await this.git(['update-ref', PUSH_REF, commit]);
|
|
205
215
|
const bundlePath = this.spoolPath('push');
|
|
206
216
|
try {
|
|
207
|
-
await this.git([
|
|
217
|
+
await this.git([
|
|
218
|
+
'-c',
|
|
219
|
+
'pack.threads=1',
|
|
220
|
+
'bundle',
|
|
221
|
+
'create',
|
|
222
|
+
bundlePath,
|
|
223
|
+
PUSH_REF,
|
|
224
|
+
`^${serving}`,
|
|
225
|
+
]);
|
|
208
226
|
return await readFile(bundlePath);
|
|
209
227
|
}
|
|
210
228
|
finally {
|
|
@@ -297,7 +315,10 @@ export class SyncRepository {
|
|
|
297
315
|
* caller reads it as an answer.
|
|
298
316
|
*/
|
|
299
317
|
async git(args, options = {}) {
|
|
300
|
-
const env =
|
|
318
|
+
const env = {
|
|
319
|
+
...this.repositoryEnv(options.indexFile ?? join(this.gitDir, INDEX_FILE)),
|
|
320
|
+
...options.env,
|
|
321
|
+
};
|
|
301
322
|
const run = await this.spawnGit([...this.gitFlags(), ...args], env, options);
|
|
302
323
|
if (options.allowFailure !== true)
|
|
303
324
|
this.assertSucceeded(args, run);
|