primitive-admin 1.0.49 → 1.0.50
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 +102 -2
- package/assets/skill/skills/primitive-platform/SKILL.md +85 -30
- package/dist/bin/primitive.d.ts +2 -0
- package/dist/bin/primitive.js +66 -1
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/admins.d.ts +2 -0
- package/dist/src/commands/analytics.d.ts +2 -0
- package/dist/src/commands/apps.d.ts +2 -0
- package/dist/src/commands/apps.js +20 -0
- package/dist/src/commands/apps.js.map +1 -1
- package/dist/src/commands/auth.d.ts +2 -0
- package/dist/src/commands/blob-buckets.d.ts +2 -0
- package/dist/src/commands/catalog.d.ts +2 -0
- package/dist/src/commands/collection-type-configs.d.ts +2 -0
- package/dist/src/commands/collections.d.ts +2 -0
- package/dist/src/commands/comparisons.d.ts +2 -0
- package/dist/src/commands/cron-triggers.d.ts +2 -0
- package/dist/src/commands/cron-triggers.js +8 -15
- package/dist/src/commands/cron-triggers.js.map +1 -1
- package/dist/src/commands/database-types.d.ts +2 -0
- package/dist/src/commands/databases.d.ts +2 -0
- package/dist/src/commands/databases.js +31 -0
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.d.ts +2 -0
- package/dist/src/commands/email-templates.d.ts +2 -0
- package/dist/src/commands/env.d.ts +12 -0
- package/dist/src/commands/group-type-configs.d.ts +2 -0
- package/dist/src/commands/groups.d.ts +2 -0
- package/dist/src/commands/guides.d.ts +84 -0
- package/dist/src/commands/guides.js +201 -24
- package/dist/src/commands/guides.js.map +1 -1
- package/dist/src/commands/init.d.ts +17 -0
- package/dist/src/commands/init.js +63 -25
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.d.ts +2 -0
- package/dist/src/commands/integrations.js +22 -5
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/llm.d.ts +2 -0
- package/dist/src/commands/prompts.d.ts +2 -0
- package/dist/src/commands/rule-sets.d.ts +2 -0
- package/dist/src/commands/secrets.d.ts +2 -0
- package/dist/src/commands/skill.d.ts +2 -0
- package/dist/src/commands/sync.d.ts +113 -0
- package/dist/src/commands/sync.js +366 -12
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/tokens.d.ts +2 -0
- package/dist/src/commands/tokens.js +104 -1
- package/dist/src/commands/tokens.js.map +1 -1
- package/dist/src/commands/users.d.ts +2 -0
- package/dist/src/commands/waitlist.d.ts +2 -0
- package/dist/src/commands/waitlist.js +1 -1
- package/dist/src/commands/waitlist.js.map +1 -1
- package/dist/src/commands/webhooks.d.ts +2 -0
- package/dist/src/commands/workflows.d.ts +49 -0
- package/dist/src/commands/workflows.js +74 -21
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +1244 -0
- package/dist/src/lib/api-client.js +30 -0
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/auth-flow.d.ts +8 -0
- package/dist/src/lib/cli-manifest.d.ts +60 -0
- package/dist/src/lib/cli-manifest.js +70 -0
- package/dist/src/lib/cli-manifest.js.map +1 -0
- package/dist/src/lib/config.d.ts +37 -0
- package/dist/src/lib/confirm-prompt.d.ts +66 -0
- package/dist/src/lib/confirm-prompt.js +85 -0
- package/dist/src/lib/confirm-prompt.js.map +1 -0
- package/dist/src/lib/constants.d.ts +2 -0
- package/dist/src/lib/crash-handlers.d.ts +20 -0
- package/dist/src/lib/crash-handlers.js +49 -0
- package/dist/src/lib/crash-handlers.js.map +1 -0
- package/dist/src/lib/credentials-store.d.ts +79 -0
- package/dist/src/lib/csv.d.ts +48 -0
- package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
- package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
- package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
- package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
- package/dist/src/lib/db-codegen/dbTemplates.js +31 -10
- package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
- package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
- package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
- package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +62 -0
- package/dist/src/lib/fetch.d.ts +5 -0
- package/dist/src/lib/init-config.d.ts +46 -0
- package/dist/src/lib/init-config.js +7 -0
- package/dist/src/lib/init-config.js.map +1 -1
- package/dist/src/lib/migration-nag.d.ts +49 -0
- package/dist/src/lib/output.d.ts +49 -0
- package/dist/src/lib/output.js +25 -1
- package/dist/src/lib/output.js.map +1 -1
- package/dist/src/lib/paginate.d.ts +33 -0
- package/dist/src/lib/project-config.d.ts +97 -0
- package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
- package/dist/src/lib/resolve-platform.d.ts +45 -0
- package/dist/src/lib/resolve-platform.js +43 -0
- package/dist/src/lib/resolve-platform.js.map +1 -0
- package/dist/src/lib/skill-installer.d.ts +23 -0
- package/dist/src/lib/snapshots.d.ts +99 -0
- package/dist/src/lib/snapshots.js +357 -0
- package/dist/src/lib/snapshots.js.map +1 -0
- package/dist/src/lib/sync-paths.d.ts +72 -0
- package/dist/src/lib/sync-paths.js +29 -1
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/template.d.ts +93 -0
- package/dist/src/lib/token-inject.d.ts +56 -0
- package/dist/src/lib/token-inject.js +204 -0
- package/dist/src/lib/token-inject.js.map +1 -0
- package/dist/src/lib/toml-database-config.d.ts +132 -0
- package/dist/src/lib/toml-params-validator.d.ts +95 -0
- package/dist/src/lib/version-check.d.ts +10 -0
- package/dist/src/lib/workflow-fragments.d.ts +41 -0
- package/dist/src/lib/workflow-toml-validator.d.ts +86 -0
- package/dist/src/lib/workflow-toml-validator.js +31 -1
- package/dist/src/lib/workflow-toml-validator.js.map +1 -1
- package/dist/src/types/index.d.ts +513 -0
- package/dist/src/validators.d.ts +64 -0
- package/dist/src/validators.js +63 -0
- package/dist/src/validators.js.map +1 -0
- package/package.json +7 -1
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync snapshots — point-in-time backups of a sync directory taken before a
|
|
3
|
+
* destructive `sync pull` overwrites local TOML state (issue #578, Phase 1).
|
|
4
|
+
*
|
|
5
|
+
* A snapshot is a full recursive copy of the live sync tree (including
|
|
6
|
+
* `.primitive-sync.json`, the content-hash baseline) into a timestamped slot
|
|
7
|
+
* under the snapshots root:
|
|
8
|
+
*
|
|
9
|
+
* <snapshotsRoot>/<YYYY-MM-DDTHH-MM-SS>/
|
|
10
|
+
* <full pre-pull copy of the sync tree, incl. .primitive-sync.json>
|
|
11
|
+
* .snapshot-complete # integrity marker, written LAST
|
|
12
|
+
* .audit-id # ULID cross-link (Phase 2; absent in P1)
|
|
13
|
+
*
|
|
14
|
+
* The `.snapshot-complete` marker is written last so a partial/interrupted
|
|
15
|
+
* copy is detectable: `restoreSnapshot` refuses any snapshot missing it.
|
|
16
|
+
*
|
|
17
|
+
* Snapshots fail LOUD — `createSnapshot` throws if it can't write a complete
|
|
18
|
+
* snapshot. The caller (sync pull) aborts before touching any local file so we
|
|
19
|
+
* never perform a destructive pull without a recoverable backup.
|
|
20
|
+
*
|
|
21
|
+
* Restore is staged-then-swapped: the snapshot is materialized into a sibling
|
|
22
|
+
* temp dir, then the live `configDir` is atomically replaced, so a mid-restore
|
|
23
|
+
* crash never leaves a half-written tree.
|
|
24
|
+
*/
|
|
25
|
+
import { existsSync, readdirSync, statSync, writeFileSync, readFileSync, cpSync, rmSync, renameSync, mkdirSync, mkdtempSync, } from "fs";
|
|
26
|
+
import { join, dirname, resolve, relative, isAbsolute } from "path";
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if `child` is `parent` or lives somewhere beneath it. Used to
|
|
29
|
+
* detect the legacy `--dir` case where the snapshots root sits INSIDE the sync
|
|
30
|
+
* tree (`<userDir>/.snapshots/`) — we must not copy that subtree into the
|
|
31
|
+
* snapshot, or `cpSync` recurses into itself.
|
|
32
|
+
*/
|
|
33
|
+
function isInside(parent, child) {
|
|
34
|
+
const rel = relative(resolve(parent), resolve(child));
|
|
35
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
36
|
+
}
|
|
37
|
+
/** Marker file written LAST inside a snapshot to prove it completed. */
|
|
38
|
+
export const SNAPSHOT_COMPLETE_MARKER = ".snapshot-complete";
|
|
39
|
+
/** Cross-link to the audit entry that produced the snapshot (Phase 2). */
|
|
40
|
+
export const SNAPSHOT_AUDIT_ID_MARKER = ".audit-id";
|
|
41
|
+
/**
|
|
42
|
+
* Produce the timestamp directory name for a snapshot, e.g.
|
|
43
|
+
* `2026-06-01T17-42-18`. Colons (illegal on Windows, awkward everywhere) are
|
|
44
|
+
* replaced with hyphens; milliseconds and the trailing `Z` are dropped.
|
|
45
|
+
*/
|
|
46
|
+
function snapshotTimestamp(now = new Date()) {
|
|
47
|
+
// 2026-06-01T17:42:18.401Z -> 2026-06-01T17-42-18
|
|
48
|
+
return now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Returns true if `dir` exists and contains at least one entry. A fresh
|
|
52
|
+
* project (no prior sync dir, or an empty one) has nothing to back up.
|
|
53
|
+
*/
|
|
54
|
+
function dirHasContent(dir) {
|
|
55
|
+
if (!existsSync(dir))
|
|
56
|
+
return false;
|
|
57
|
+
try {
|
|
58
|
+
return readdirSync(dir).length > 0;
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Snapshot the current `syncDir` into a new timestamped slot under
|
|
66
|
+
* `snapshotsRoot`. Returns the created snapshot, or `null` when there's
|
|
67
|
+
* nothing to back up (missing/empty `syncDir`).
|
|
68
|
+
*
|
|
69
|
+
* Fails LOUD: throws if the copy or marker write fails. The partial snapshot
|
|
70
|
+
* dir is best-effort cleaned up before rethrowing so a failed snapshot never
|
|
71
|
+
* leaves a marker-less husk behind.
|
|
72
|
+
*/
|
|
73
|
+
export function createSnapshot(syncDir, snapshotsRoot, opts = {}) {
|
|
74
|
+
if (!dirHasContent(syncDir)) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const id = snapshotTimestamp(opts.now);
|
|
78
|
+
const dest = join(snapshotsRoot, id);
|
|
79
|
+
// Guard against an id collision (two pulls within the same second). Append a
|
|
80
|
+
// numeric suffix until we find a free slot so a rapid second pull can't
|
|
81
|
+
// clobber the first snapshot.
|
|
82
|
+
let finalId = id;
|
|
83
|
+
let finalDest = dest;
|
|
84
|
+
let suffix = 1;
|
|
85
|
+
while (existsSync(finalDest)) {
|
|
86
|
+
finalId = `${id}-${suffix}`;
|
|
87
|
+
finalDest = join(snapshotsRoot, finalId);
|
|
88
|
+
suffix += 1;
|
|
89
|
+
}
|
|
90
|
+
// Fork 4: under legacy `--dir`, the snapshots root lives INSIDE the sync
|
|
91
|
+
// tree (`<userDir>/.snapshots/`). Node's cpSync refuses to copy a directory
|
|
92
|
+
// into its own subtree (ERR_FS_CP_EINVAL) even with a filter, so in that
|
|
93
|
+
// case we stage the copy into an external temp dir (excluding the backups
|
|
94
|
+
// subtree) and then move it into place.
|
|
95
|
+
const rootIsInside = isInside(syncDir, snapshotsRoot);
|
|
96
|
+
const snapshotsRootResolved = resolve(snapshotsRoot);
|
|
97
|
+
const filter = rootIsInside
|
|
98
|
+
? (src) => !isInside(snapshotsRootResolved, src)
|
|
99
|
+
: undefined;
|
|
100
|
+
let externalStage = null;
|
|
101
|
+
try {
|
|
102
|
+
mkdirSync(snapshotsRoot, { recursive: true });
|
|
103
|
+
if (rootIsInside) {
|
|
104
|
+
// Copy to a sibling of syncDir first (outside the sync tree, so no
|
|
105
|
+
// self-recursion), then move into the inside-the-tree slot. Staging in
|
|
106
|
+
// the parent keeps the move same-filesystem (avoids cross-device EXDEV
|
|
107
|
+
// that a tmpdir stage could hit).
|
|
108
|
+
const parent = dirname(resolve(syncDir));
|
|
109
|
+
mkdirSync(parent, { recursive: true });
|
|
110
|
+
externalStage = mkdtempSync(join(parent, ".primitive-snapshot-stage-"));
|
|
111
|
+
const stageTree = join(externalStage, "tree");
|
|
112
|
+
cpSync(syncDir, stageTree, {
|
|
113
|
+
recursive: true,
|
|
114
|
+
dereference: false,
|
|
115
|
+
...(filter ? { filter } : {}),
|
|
116
|
+
});
|
|
117
|
+
renameSync(stageTree, finalDest);
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// Full recursive copy. `dereference: false` keeps symlinks from being
|
|
121
|
+
// silently followed (edge case: symlinks inside the sync tree).
|
|
122
|
+
cpSync(syncDir, finalDest, { recursive: true, dereference: false });
|
|
123
|
+
}
|
|
124
|
+
if (opts.auditId) {
|
|
125
|
+
writeFileSync(join(finalDest, SNAPSHOT_AUDIT_ID_MARKER), opts.auditId);
|
|
126
|
+
}
|
|
127
|
+
// Integrity marker written LAST — its presence proves the copy finished.
|
|
128
|
+
writeFileSync(join(finalDest, SNAPSHOT_COMPLETE_MARKER), new Date().toISOString());
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
// Best-effort cleanup of the partial snapshot so we don't leave an
|
|
132
|
+
// incomplete (marker-less) directory around.
|
|
133
|
+
try {
|
|
134
|
+
rmSync(finalDest, { recursive: true, force: true });
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// ignore cleanup failure
|
|
138
|
+
}
|
|
139
|
+
throw err;
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
if (externalStage) {
|
|
143
|
+
try {
|
|
144
|
+
rmSync(externalStage, { recursive: true, force: true });
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// ignore
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return { id: finalId, path: finalDest };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* List snapshots under `snapshotsRoot`, newest-first. Returns an empty array
|
|
155
|
+
* if the root doesn't exist. Each entry reports whether it's complete and any
|
|
156
|
+
* cross-linked audit id.
|
|
157
|
+
*/
|
|
158
|
+
export function listSnapshots(snapshotsRoot) {
|
|
159
|
+
if (!existsSync(snapshotsRoot))
|
|
160
|
+
return [];
|
|
161
|
+
let names;
|
|
162
|
+
try {
|
|
163
|
+
names = readdirSync(snapshotsRoot);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
return [];
|
|
167
|
+
}
|
|
168
|
+
const infos = [];
|
|
169
|
+
for (const name of names) {
|
|
170
|
+
const path = join(snapshotsRoot, name);
|
|
171
|
+
let stat;
|
|
172
|
+
try {
|
|
173
|
+
stat = statSync(path);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
if (!stat.isDirectory())
|
|
179
|
+
continue;
|
|
180
|
+
const complete = existsSync(join(path, SNAPSHOT_COMPLETE_MARKER));
|
|
181
|
+
let auditId = null;
|
|
182
|
+
const auditPath = join(path, SNAPSHOT_AUDIT_ID_MARKER);
|
|
183
|
+
if (existsSync(auditPath)) {
|
|
184
|
+
try {
|
|
185
|
+
auditId = readFileSync(auditPath, "utf-8").trim() || null;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
auditId = null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
infos.push({ id: name, path, complete, auditId, createdAt: stat.mtime });
|
|
192
|
+
}
|
|
193
|
+
// Newest first. The id is a sortable timestamp string, so a reverse string
|
|
194
|
+
// sort matches chronological order (and stays correct for the `-N` suffixed
|
|
195
|
+
// collision variants within the same second).
|
|
196
|
+
infos.sort((a, b) => (a.id < b.id ? 1 : a.id > b.id ? -1 : 0));
|
|
197
|
+
return infos;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Resolve a snapshot by id within `snapshotsRoot`. Accepts either the full
|
|
201
|
+
* timestamp directory name OR a unique prefix of at least `minPrefix`
|
|
202
|
+
* characters (default 8). Returns the matching snapshot, or throws a clear
|
|
203
|
+
* error on no-match / ambiguous-prefix.
|
|
204
|
+
*
|
|
205
|
+
* When `id` is omitted, resolves to the most recent COMPLETE snapshot.
|
|
206
|
+
*/
|
|
207
|
+
export function resolveSnapshot(snapshotsRoot, id, minPrefix = 8) {
|
|
208
|
+
const all = listSnapshots(snapshotsRoot);
|
|
209
|
+
if (all.length === 0) {
|
|
210
|
+
throw new Error(`No snapshots found in ${snapshotsRoot}`);
|
|
211
|
+
}
|
|
212
|
+
if (!id) {
|
|
213
|
+
const mostRecentComplete = all.find((s) => s.complete);
|
|
214
|
+
if (!mostRecentComplete) {
|
|
215
|
+
throw new Error(`No complete snapshots found in ${snapshotsRoot} (all are missing the ${SNAPSHOT_COMPLETE_MARKER} marker).`);
|
|
216
|
+
}
|
|
217
|
+
return mostRecentComplete;
|
|
218
|
+
}
|
|
219
|
+
// Exact match wins.
|
|
220
|
+
const exact = all.find((s) => s.id === id);
|
|
221
|
+
if (exact)
|
|
222
|
+
return exact;
|
|
223
|
+
// Prefix match (must be unique and >= minPrefix chars).
|
|
224
|
+
if (id.length < minPrefix) {
|
|
225
|
+
throw new Error(`Snapshot id "${id}" is too short — provide at least ${minPrefix} characters or the full timestamp.`);
|
|
226
|
+
}
|
|
227
|
+
const matches = all.filter((s) => s.id.startsWith(id));
|
|
228
|
+
if (matches.length === 0) {
|
|
229
|
+
throw new Error(`No snapshot matches "${id}" in ${snapshotsRoot}`);
|
|
230
|
+
}
|
|
231
|
+
if (matches.length > 1) {
|
|
232
|
+
throw new Error(`Snapshot prefix "${id}" is ambiguous (${matches.length} matches: ${matches
|
|
233
|
+
.map((m) => m.id)
|
|
234
|
+
.join(", ")}). Provide more characters.`);
|
|
235
|
+
}
|
|
236
|
+
return matches[0];
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Restore a snapshot into `syncDir`, replacing whatever is there.
|
|
240
|
+
*
|
|
241
|
+
* Refuses to restore a snapshot missing the `.snapshot-complete` marker
|
|
242
|
+
* (partial/corrupted snapshot). The internal marker files (`.snapshot-complete`,
|
|
243
|
+
* `.audit-id`) are NOT copied into the restored tree.
|
|
244
|
+
*
|
|
245
|
+
* Staged-then-swapped: the snapshot is materialized into a sibling temp dir,
|
|
246
|
+
* then `syncDir` is atomically replaced, so a mid-restore crash never leaves a
|
|
247
|
+
* half-written tree.
|
|
248
|
+
*
|
|
249
|
+
* `opts.preserveDir` names a directory (the snapshots root) that lives INSIDE
|
|
250
|
+
* `syncDir` and must survive the full-tree swap — the Fork-4 legacy `--dir`
|
|
251
|
+
* case, where backups sit at `<syncDir>/.snapshots/`. Without preserving it,
|
|
252
|
+
* the swap would wipe the user's entire snapshot history on restore.
|
|
253
|
+
*/
|
|
254
|
+
export function restoreSnapshot(snapshotPath, syncDir, opts = {}) {
|
|
255
|
+
if (!existsSync(snapshotPath)) {
|
|
256
|
+
throw new Error(`Snapshot not found: ${snapshotPath}`);
|
|
257
|
+
}
|
|
258
|
+
if (!existsSync(join(snapshotPath, SNAPSHOT_COMPLETE_MARKER))) {
|
|
259
|
+
throw new Error(`Refusing to restore: snapshot ${snapshotPath} is missing the ${SNAPSHOT_COMPLETE_MARKER} marker (partial/corrupted snapshot).`);
|
|
260
|
+
}
|
|
261
|
+
const parent = dirname(syncDir);
|
|
262
|
+
mkdirSync(parent, { recursive: true });
|
|
263
|
+
const unique = `${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
|
|
264
|
+
const stageDir = join(parent, `.primitive-restore-stage-${unique}`);
|
|
265
|
+
const oldDir = join(parent, `.primitive-restore-old-${unique}`);
|
|
266
|
+
// Determine whether a backups dir living inside syncDir needs preserving.
|
|
267
|
+
const preserve = opts.preserveDir &&
|
|
268
|
+
isInside(syncDir, opts.preserveDir) &&
|
|
269
|
+
!isInside(snapshotPath, opts.preserveDir) // don't preserve a dir that's part of the snapshot
|
|
270
|
+
? resolve(opts.preserveDir)
|
|
271
|
+
: null;
|
|
272
|
+
const preserveStash = preserve
|
|
273
|
+
? join(parent, `.primitive-restore-keep-${unique}`)
|
|
274
|
+
: null;
|
|
275
|
+
try {
|
|
276
|
+
// Materialize the snapshot into the staging dir.
|
|
277
|
+
cpSync(snapshotPath, stageDir, { recursive: true, dereference: false });
|
|
278
|
+
// Drop the snapshot-internal marker files from the restored tree.
|
|
279
|
+
for (const marker of [SNAPSHOT_COMPLETE_MARKER, SNAPSHOT_AUDIT_ID_MARKER]) {
|
|
280
|
+
const markerPath = join(stageDir, marker);
|
|
281
|
+
if (existsSync(markerPath)) {
|
|
282
|
+
rmSync(markerPath, { force: true });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Atomic-ish swap: move the live dir aside, move staging into place, then
|
|
286
|
+
// delete the old dir. Same-filesystem renames are atomic.
|
|
287
|
+
if (existsSync(syncDir)) {
|
|
288
|
+
renameSync(syncDir, oldDir);
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
renameSync(stageDir, syncDir);
|
|
292
|
+
}
|
|
293
|
+
catch (err) {
|
|
294
|
+
// Swap failed — try to roll the original back so we never leave the
|
|
295
|
+
// user with no sync dir at all.
|
|
296
|
+
if (existsSync(oldDir) && !existsSync(syncDir)) {
|
|
297
|
+
try {
|
|
298
|
+
renameSync(oldDir, syncDir);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
// ignore rollback failure; original error is more useful
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
throw err;
|
|
305
|
+
}
|
|
306
|
+
// Re-attach the preserved backups dir (Fork-4 `--dir` mode) into the
|
|
307
|
+
// freshly-restored tree, pulling it out of the displaced original.
|
|
308
|
+
if (preserve && preserveStash) {
|
|
309
|
+
const rel = relative(resolve(syncDir), preserve);
|
|
310
|
+
const fromOld = join(oldDir, rel);
|
|
311
|
+
const intoNew = join(syncDir, rel);
|
|
312
|
+
if (existsSync(fromOld) && !existsSync(intoNew)) {
|
|
313
|
+
mkdirSync(dirname(intoNew), { recursive: true });
|
|
314
|
+
renameSync(fromOld, intoNew);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Success — remove the displaced original.
|
|
318
|
+
if (existsSync(oldDir)) {
|
|
319
|
+
rmSync(oldDir, { recursive: true, force: true });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
// Clean up any leftover staging/old/stash dirs on failure.
|
|
324
|
+
for (const dir of [stageDir, oldDir, preserveStash].filter((d) => Boolean(d))) {
|
|
325
|
+
if (existsSync(dir)) {
|
|
326
|
+
try {
|
|
327
|
+
rmSync(dir, { recursive: true, force: true });
|
|
328
|
+
}
|
|
329
|
+
catch {
|
|
330
|
+
// ignore
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Prune snapshots older than `retentionDays` from `snapshotsRoot`. Only the
|
|
338
|
+
* given slot is touched (cross-slot retention is independent — each slot has
|
|
339
|
+
* its own snapshots root). No-op if the root doesn't exist.
|
|
340
|
+
*/
|
|
341
|
+
export function pruneSnapshots(snapshotsRoot, retentionDays = 28) {
|
|
342
|
+
if (!existsSync(snapshotsRoot))
|
|
343
|
+
return;
|
|
344
|
+
const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
|
|
345
|
+
for (const info of listSnapshots(snapshotsRoot)) {
|
|
346
|
+
if (info.createdAt.getTime() < cutoff) {
|
|
347
|
+
try {
|
|
348
|
+
rmSync(info.path, { recursive: true, force: true });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
// Best-effort prune — a failure to delete one stale snapshot must not
|
|
352
|
+
// abort the pull that triggered the prune.
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
//# sourceMappingURL=snapshots.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshots.js","sourceRoot":"","sources":["../../../src/lib/snapshots.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sync data path resolution.
|
|
3
|
+
*
|
|
4
|
+
* Per issue #373, sync state is scoped by environment AND by app:
|
|
5
|
+
*
|
|
6
|
+
* <projectRoot>/.primitive/sync/<env>/<appId>/
|
|
7
|
+
*
|
|
8
|
+
* This ensures that switching environments (dev -> prod) or apps never
|
|
9
|
+
* clobbers sync state from another slot. Push to dev and then to prod
|
|
10
|
+
* see independent "last synced" pointers, so prod won't falsely believe
|
|
11
|
+
* dev's changes are already in sync.
|
|
12
|
+
*
|
|
13
|
+
* When the user still passes `--dir <path>` (the legacy flag) we honor
|
|
14
|
+
* it and store sync state inside that directory, same as before. This
|
|
15
|
+
* keeps pre-project-config workflows working.
|
|
16
|
+
*/
|
|
17
|
+
export interface ResolveSyncDirOptions {
|
|
18
|
+
/** The resolved app ID for this sync operation. */
|
|
19
|
+
appId: string;
|
|
20
|
+
/**
|
|
21
|
+
* If the user passed `--dir <path>` on the command line, forward it
|
|
22
|
+
* here. Explicit user input always wins.
|
|
23
|
+
*/
|
|
24
|
+
userDir?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Returns the directory that should hold TOML files + sync state for the
|
|
28
|
+
* current sync operation.
|
|
29
|
+
*
|
|
30
|
+
* Resolution order:
|
|
31
|
+
* 1. If userDir is set, return it as-is (legacy behavior).
|
|
32
|
+
* 2. If we're in project mode, return
|
|
33
|
+
* <projectRoot>/.primitive/sync/<env>/<appId>
|
|
34
|
+
* 3. Otherwise fall back to "./config" (the old default).
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolveSyncDir(options: ResolveSyncDirOptions): string;
|
|
37
|
+
/**
|
|
38
|
+
* Returns the snapshot-backups root for the slot a given sync operation
|
|
39
|
+
* belongs to (issue #578, Phase 1). Snapshots reuse the SAME resolution chain
|
|
40
|
+
* as `resolveSyncDir` (the resolved `ResolvedEnvironment` + `safeEnv()`), so
|
|
41
|
+
* the backup tree can never drift from the live sync tree — there is no
|
|
42
|
+
* parallel `{projectRoot, env, appId}` mini-model.
|
|
43
|
+
*
|
|
44
|
+
* Resolution order mirrors `resolveSyncDir`:
|
|
45
|
+
* 1. If userDir (legacy `--dir`) is set, snapshots live in a sibling
|
|
46
|
+
* `<userDir>/.snapshots/` so revert works without a project root
|
|
47
|
+
* (Fork 4 — designed explicitly).
|
|
48
|
+
* 2. In project mode, the project sibling
|
|
49
|
+
* `<projectRoot>/.primitive/sync-backups/<env>/<appId>/`.
|
|
50
|
+
* 3. Otherwise (bare `./config` legacy default), `./config-snapshots`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function resolveSnapshotsRoot(options: ResolveSyncDirOptions): string;
|
|
53
|
+
export declare function assertSafePathComponent(value: string, label: string): void;
|
|
54
|
+
/**
|
|
55
|
+
* Returns true if the path we'd use for sync data is the auto-resolved
|
|
56
|
+
* project-mode path (vs. a user-supplied --dir).
|
|
57
|
+
*/
|
|
58
|
+
export declare function isAutoResolvedSyncDir(userDir: string | undefined): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Ensures the sync directory exists, creating it (and parents) if
|
|
61
|
+
* necessary. Returns the path.
|
|
62
|
+
*/
|
|
63
|
+
export declare function ensureSyncDir(options: ResolveSyncDirOptions): string;
|
|
64
|
+
/**
|
|
65
|
+
* Checks whether an old-style sync state file exists at `./config/.primitive-sync.json`
|
|
66
|
+
* (the pre-project-config default path). Returns a warning message if found and
|
|
67
|
+
* the current operation is using a different (project-mode) path, or null otherwise.
|
|
68
|
+
*
|
|
69
|
+
* Callers can display this once at the start of sync commands so users know
|
|
70
|
+
* their old sync data won't be picked up automatically.
|
|
71
|
+
*/
|
|
72
|
+
export declare function checkLegacySyncMigration(currentSyncDir: string): string | null;
|
|
@@ -41,7 +41,35 @@ export function resolveSyncDir(options) {
|
|
|
41
41
|
}
|
|
42
42
|
return "./config";
|
|
43
43
|
}
|
|
44
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Returns the snapshot-backups root for the slot a given sync operation
|
|
46
|
+
* belongs to (issue #578, Phase 1). Snapshots reuse the SAME resolution chain
|
|
47
|
+
* as `resolveSyncDir` (the resolved `ResolvedEnvironment` + `safeEnv()`), so
|
|
48
|
+
* the backup tree can never drift from the live sync tree — there is no
|
|
49
|
+
* parallel `{projectRoot, env, appId}` mini-model.
|
|
50
|
+
*
|
|
51
|
+
* Resolution order mirrors `resolveSyncDir`:
|
|
52
|
+
* 1. If userDir (legacy `--dir`) is set, snapshots live in a sibling
|
|
53
|
+
* `<userDir>/.snapshots/` so revert works without a project root
|
|
54
|
+
* (Fork 4 — designed explicitly).
|
|
55
|
+
* 2. In project mode, the project sibling
|
|
56
|
+
* `<projectRoot>/.primitive/sync-backups/<env>/<appId>/`.
|
|
57
|
+
* 3. Otherwise (bare `./config` legacy default), `./config-snapshots`.
|
|
58
|
+
*/
|
|
59
|
+
export function resolveSnapshotsRoot(options) {
|
|
60
|
+
if (options.userDir) {
|
|
61
|
+
return join(options.userDir, ".snapshots");
|
|
62
|
+
}
|
|
63
|
+
const env = safeEnv();
|
|
64
|
+
if (env) {
|
|
65
|
+
// Same defence-in-depth as resolveSyncDir: never trust appId blindly when
|
|
66
|
+
// building a file-system path.
|
|
67
|
+
assertSafePathComponent(options.appId, "appId");
|
|
68
|
+
return join(env.projectRoot, ".primitive", "sync-backups", env.name, options.appId);
|
|
69
|
+
}
|
|
70
|
+
return "./config-snapshots";
|
|
71
|
+
}
|
|
72
|
+
export function assertSafePathComponent(value, label) {
|
|
45
73
|
if (typeof value !== "string" ||
|
|
46
74
|
value.length === 0 ||
|
|
47
75
|
/[\\/]/.test(value) ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-paths.js","sourceRoot":"","sources":["../../../src/lib/sync-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAY1D;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC;IAE5C,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,IAAI,GAAG,EAAE,CAAC;QACR,wEAAwE;QACxE,yEAAyE;QACzE,kEAAkE;QAClE,oDAAoD;QACpD,uBAAuB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CACT,GAAG,CAAC,WAAW,EACf,YAAY,EACZ,MAAM,EACN,GAAG,CAAC,IAAI,EACR,OAAO,CAAC,KAAK,CACd,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"sync-paths.js","sourceRoot":"","sources":["../../../src/lib/sync-paths.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAY1D;;;;;;;;;GASG;AACH,MAAM,UAAU,cAAc,CAAC,OAA8B;IAC3D,IAAI,OAAO,CAAC,OAAO;QAAE,OAAO,OAAO,CAAC,OAAO,CAAC;IAE5C,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,IAAI,GAAG,EAAE,CAAC;QACR,wEAAwE;QACxE,yEAAyE;QACzE,kEAAkE;QAClE,oDAAoD;QACpD,uBAAuB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CACT,GAAG,CAAC,WAAW,EACf,YAAY,EACZ,MAAM,EACN,GAAG,CAAC,IAAI,EACR,OAAO,CAAC,KAAK,CACd,CAAC;IACJ,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAA8B;IACjE,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;IAC7C,CAAC;IAED,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,IAAI,GAAG,EAAE,CAAC;QACR,0EAA0E;QAC1E,+BAA+B;QAC/B,uBAAuB,CAAC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAChD,OAAO,IAAI,CACT,GAAG,CAAC,WAAW,EACf,YAAY,EACZ,cAAc,EACd,GAAG,CAAC,IAAI,EACR,OAAO,CAAC,KAAK,CACd,CAAC;IACJ,CAAC;IAED,OAAO,oBAAoB,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAa,EAAE,KAAa;IAClE,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,KAAK,CAAC,MAAM,KAAK,CAAC;QAClB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QACnB,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QACpB,KAAK,KAAK,GAAG;QACb,KAAK,KAAK,EAAE,EACZ,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,WAAW,KAAK,mBAAmB,KAAK,EAAE,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAA2B;IAC/D,IAAI,OAAO;QAAE,OAAO,KAAK,CAAC;IAC1B,OAAO,OAAO,EAAE,KAAK,IAAI,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,OAA8B;IAC1D,MAAM,GAAG,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,OAAO;IACd,IAAI,CAAC;QACH,OAAO,qBAAqB,EAAE,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,wBAAwB,CAAC,cAAsB;IAC7D,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,EAAE,sBAAsB,CAAC,CAAC;IAC/D,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACzC,sEAAsE;IACtE,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACtC,IAAI,cAAc,KAAK,SAAS;QAAE,OAAO,IAAI,CAAC;IAC9C,OAAO,CACL,4DAA4D;QAC5D,0BAA0B,cAAc,IAAI;QAC5C,6DAA6D,CAC9D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
declare const IOS_TEMPLATE_URL = "https://api.github.com/repos/Primitive-Labs/primitive-ios-template/tarball/main";
|
|
2
|
+
export interface DownloadTemplateOptions {
|
|
3
|
+
targetDir: string;
|
|
4
|
+
templateUrl?: string;
|
|
5
|
+
onProgress?: (status: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Check if a directory is empty.
|
|
9
|
+
*/
|
|
10
|
+
export declare function isDirectoryEmpty(dir: string): boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Downloads and extracts the Primitive app template.
|
|
13
|
+
* The tarball extracts to a subfolder, so we strip that prefix by moving contents up.
|
|
14
|
+
*/
|
|
15
|
+
export declare function downloadAndExtractTemplate(options: DownloadTemplateOptions): Promise<void>;
|
|
16
|
+
export interface UpdateEnvResult {
|
|
17
|
+
envUpdated: boolean;
|
|
18
|
+
envProductionUpdated: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Updates .env and .env.production files with the app ID and server URLs.
|
|
22
|
+
* For .env, if it doesn't exist but .env.example does, copies from example first.
|
|
23
|
+
* Returns information about which files were updated.
|
|
24
|
+
*/
|
|
25
|
+
export declare function updateEnvFile(targetDir: string, appId: string, serverUrl: string, appName?: string): UpdateEnvResult;
|
|
26
|
+
/**
|
|
27
|
+
* Runs pnpm install in the target directory.
|
|
28
|
+
* Uses npx to ensure corepack/packageManager compatibility.
|
|
29
|
+
*/
|
|
30
|
+
export declare function runPnpmInstall(targetDir: string): Promise<void>;
|
|
31
|
+
/**
|
|
32
|
+
* Checks if pnpm is installed globally.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isPnpmInstalled(): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Installs pnpm globally using npm.
|
|
37
|
+
*/
|
|
38
|
+
export declare function installPnpmGlobally(): Promise<void>;
|
|
39
|
+
/**
|
|
40
|
+
* Checks if the primitive CLI is installed and available in PATH.
|
|
41
|
+
* Checks for the `primitive` command directly, which covers installs via
|
|
42
|
+
* npm, pnpm, yarn, or any other package manager.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isPrimitiveCliInstalled(): Promise<boolean>;
|
|
45
|
+
export declare function gitInitRepo(targetDir: string): Promise<void>;
|
|
46
|
+
export declare function gitInitialCommit(targetDir: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Checks whether a TCP port is currently in use on localhost.
|
|
49
|
+
* Tries both IPv4 (127.0.0.1) and IPv6 (::1) since a server may only
|
|
50
|
+
* bind to one address family.
|
|
51
|
+
*/
|
|
52
|
+
export declare function isPortFree(port: number): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* Pins the dev server port in vite.config.{ts,js,mts,mjs} by injecting or
|
|
55
|
+
* updating the `server.port` field. Returns true if a config file was found
|
|
56
|
+
* and patched.
|
|
57
|
+
*/
|
|
58
|
+
export declare function updateViteConfig(targetDir: string, port: number): boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Updates port-sensitive env vars (VITE_OAUTH_REDIRECT_URI, VITE_BASE_URL)
|
|
61
|
+
* in the project's .env file to point at the given localhost port.
|
|
62
|
+
*/
|
|
63
|
+
export declare function updateEnvPort(targetDir: string, port: number): void;
|
|
64
|
+
/**
|
|
65
|
+
* Starting from `startPort` (default 5173), finds the first free port and
|
|
66
|
+
* returns which ports were already occupied and which port vite will bind to.
|
|
67
|
+
* `exhausted` is true when all checked ports were occupied and the returned
|
|
68
|
+
* port is a best-guess beyond the checked range.
|
|
69
|
+
*/
|
|
70
|
+
export declare function findDevPort(startPort?: number, maxChecks?: number): Promise<{
|
|
71
|
+
availablePort: number;
|
|
72
|
+
takenPorts: number[];
|
|
73
|
+
exhausted: boolean;
|
|
74
|
+
}>;
|
|
75
|
+
export { IOS_TEMPLATE_URL };
|
|
76
|
+
/**
|
|
77
|
+
* Updates primitive.json in the iOS template with app ID, server URL, and app name.
|
|
78
|
+
* Replaces __PRIMITIVE_*__ placeholder values.
|
|
79
|
+
* Returns true if the file was found and updated.
|
|
80
|
+
*/
|
|
81
|
+
export declare function updatePrimitiveJson(targetDir: string, appId: string, serverUrl: string, appName?: string): boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Checks if Xcode command-line tools are installed.
|
|
84
|
+
*/
|
|
85
|
+
export declare function isXcodeInstalled(): Promise<boolean>;
|
|
86
|
+
/**
|
|
87
|
+
* Runs `swift package resolve` in the target directory to download SPM dependencies.
|
|
88
|
+
*/
|
|
89
|
+
export declare function runSwiftPackageResolve(targetDir: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Installs the primitive CLI (primitive-admin) globally using npm.
|
|
92
|
+
*/
|
|
93
|
+
export declare function installPrimitiveCli(): Promise<void>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the `primitive tokens inject` command (issue #420).
|
|
3
|
+
*
|
|
4
|
+
* Kept separate from the commander wrapper in `commands/tokens.ts` so the
|
|
5
|
+
* snippet building, shared-secret generation, and one-shot localhost server
|
|
6
|
+
* can be unit-tested without a live backend or a spawned CLI process.
|
|
7
|
+
*/
|
|
8
|
+
/** Default TTL for injected tokens — short-lived per the #420 design (30 minutes). */
|
|
9
|
+
export declare const DEFAULT_INJECT_TTL = "30m";
|
|
10
|
+
/** Output modes for `--print`. `snippet` is the default (Fork 5). */
|
|
11
|
+
export type PrintMode = "snippet" | "env" | "token";
|
|
12
|
+
/**
|
|
13
|
+
* Build the browser console one-liner that applies a token via the
|
|
14
|
+
* `window.__PRIMITIVE_SET_TOKEN__` hook installed by `<DevTools>`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildSnippet(token: string): string;
|
|
17
|
+
/** Render the output for a given `--print` mode. */
|
|
18
|
+
export declare function buildPrintOutput(mode: PrintMode, token: string): string;
|
|
19
|
+
/** Generate a random, URL-safe shared secret for the `--serve` endpoint. */
|
|
20
|
+
export declare function generateSharedSecret(): string;
|
|
21
|
+
/**
|
|
22
|
+
* Copy text to the system clipboard using whatever native tool is available.
|
|
23
|
+
* No new npm dependency — shells out to `pbcopy` (macOS), `xclip`/`xsel`
|
|
24
|
+
* (Linux), or `clip` (Windows). Returns `true` on success, `false` if no
|
|
25
|
+
* clipboard tool is available or the copy failed (caller degrades gracefully).
|
|
26
|
+
*/
|
|
27
|
+
export declare function copyToClipboard(text: string): boolean;
|
|
28
|
+
export interface ServeTokenHandle {
|
|
29
|
+
/** Full URL (incl. shared-secret query param) the consumer should fetch. */
|
|
30
|
+
url: string;
|
|
31
|
+
/** The random shared secret embedded in `url`. */
|
|
32
|
+
secret: string;
|
|
33
|
+
/** Port the server bound to (>30000). */
|
|
34
|
+
port: number;
|
|
35
|
+
/** Resolves once the token has been served once (server then closes). */
|
|
36
|
+
done: Promise<void>;
|
|
37
|
+
/** Manually shut the server down (e.g. on Ctrl-C or test cleanup). */
|
|
38
|
+
stop: () => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Boot a one-shot HTTP server bound to `127.0.0.1` that serves `token` exactly
|
|
42
|
+
* once, then shuts down. Security model:
|
|
43
|
+
* - binds loopback only (never 0.0.0.0)
|
|
44
|
+
* - requires a random shared-secret `?key=` query param; requests without the
|
|
45
|
+
* correct secret get 403 and do NOT consume the one-shot read
|
|
46
|
+
* - returns CORS headers so the documented cross-origin browser `fetch` can
|
|
47
|
+
* actually read the response; a CORS preflight (`OPTIONS`) is answered 204
|
|
48
|
+
* WITHOUT consuming the one-shot read or shutting the server down
|
|
49
|
+
* - the one-shot consumption + shutdown happen ONLY on the authorized `GET`
|
|
50
|
+
* that actually delivers the token (correct secret) — never on a preflight,
|
|
51
|
+
* a secret-rejected request, or a wrong path
|
|
52
|
+
* - retries random ports >30000 on collision (>= 5 attempts)
|
|
53
|
+
*/
|
|
54
|
+
export declare function serveTokenOnce(token: string, opts?: {
|
|
55
|
+
path?: string;
|
|
56
|
+
}): Promise<ServeTokenHandle>;
|