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.
Files changed (120) hide show
  1. package/README.md +102 -2
  2. package/assets/skill/skills/primitive-platform/SKILL.md +85 -30
  3. package/dist/bin/primitive.d.ts +2 -0
  4. package/dist/bin/primitive.js +66 -1
  5. package/dist/bin/primitive.js.map +1 -1
  6. package/dist/src/commands/admins.d.ts +2 -0
  7. package/dist/src/commands/analytics.d.ts +2 -0
  8. package/dist/src/commands/apps.d.ts +2 -0
  9. package/dist/src/commands/apps.js +20 -0
  10. package/dist/src/commands/apps.js.map +1 -1
  11. package/dist/src/commands/auth.d.ts +2 -0
  12. package/dist/src/commands/blob-buckets.d.ts +2 -0
  13. package/dist/src/commands/catalog.d.ts +2 -0
  14. package/dist/src/commands/collection-type-configs.d.ts +2 -0
  15. package/dist/src/commands/collections.d.ts +2 -0
  16. package/dist/src/commands/comparisons.d.ts +2 -0
  17. package/dist/src/commands/cron-triggers.d.ts +2 -0
  18. package/dist/src/commands/cron-triggers.js +8 -15
  19. package/dist/src/commands/cron-triggers.js.map +1 -1
  20. package/dist/src/commands/database-types.d.ts +2 -0
  21. package/dist/src/commands/databases.d.ts +2 -0
  22. package/dist/src/commands/databases.js +31 -0
  23. package/dist/src/commands/databases.js.map +1 -1
  24. package/dist/src/commands/documents.d.ts +2 -0
  25. package/dist/src/commands/email-templates.d.ts +2 -0
  26. package/dist/src/commands/env.d.ts +12 -0
  27. package/dist/src/commands/group-type-configs.d.ts +2 -0
  28. package/dist/src/commands/groups.d.ts +2 -0
  29. package/dist/src/commands/guides.d.ts +84 -0
  30. package/dist/src/commands/guides.js +201 -24
  31. package/dist/src/commands/guides.js.map +1 -1
  32. package/dist/src/commands/init.d.ts +17 -0
  33. package/dist/src/commands/init.js +63 -25
  34. package/dist/src/commands/init.js.map +1 -1
  35. package/dist/src/commands/integrations.d.ts +2 -0
  36. package/dist/src/commands/integrations.js +22 -5
  37. package/dist/src/commands/integrations.js.map +1 -1
  38. package/dist/src/commands/llm.d.ts +2 -0
  39. package/dist/src/commands/prompts.d.ts +2 -0
  40. package/dist/src/commands/rule-sets.d.ts +2 -0
  41. package/dist/src/commands/secrets.d.ts +2 -0
  42. package/dist/src/commands/skill.d.ts +2 -0
  43. package/dist/src/commands/sync.d.ts +113 -0
  44. package/dist/src/commands/sync.js +366 -12
  45. package/dist/src/commands/sync.js.map +1 -1
  46. package/dist/src/commands/tokens.d.ts +2 -0
  47. package/dist/src/commands/tokens.js +104 -1
  48. package/dist/src/commands/tokens.js.map +1 -1
  49. package/dist/src/commands/users.d.ts +2 -0
  50. package/dist/src/commands/waitlist.d.ts +2 -0
  51. package/dist/src/commands/waitlist.js +1 -1
  52. package/dist/src/commands/waitlist.js.map +1 -1
  53. package/dist/src/commands/webhooks.d.ts +2 -0
  54. package/dist/src/commands/workflows.d.ts +49 -0
  55. package/dist/src/commands/workflows.js +74 -21
  56. package/dist/src/commands/workflows.js.map +1 -1
  57. package/dist/src/lib/api-client.d.ts +1244 -0
  58. package/dist/src/lib/api-client.js +30 -0
  59. package/dist/src/lib/api-client.js.map +1 -1
  60. package/dist/src/lib/auth-flow.d.ts +8 -0
  61. package/dist/src/lib/cli-manifest.d.ts +60 -0
  62. package/dist/src/lib/cli-manifest.js +70 -0
  63. package/dist/src/lib/cli-manifest.js.map +1 -0
  64. package/dist/src/lib/config.d.ts +37 -0
  65. package/dist/src/lib/confirm-prompt.d.ts +66 -0
  66. package/dist/src/lib/confirm-prompt.js +85 -0
  67. package/dist/src/lib/confirm-prompt.js.map +1 -0
  68. package/dist/src/lib/constants.d.ts +2 -0
  69. package/dist/src/lib/crash-handlers.d.ts +20 -0
  70. package/dist/src/lib/crash-handlers.js +49 -0
  71. package/dist/src/lib/crash-handlers.js.map +1 -0
  72. package/dist/src/lib/credentials-store.d.ts +79 -0
  73. package/dist/src/lib/csv.d.ts +48 -0
  74. package/dist/src/lib/db-codegen/dbFingerprint.d.ts +10 -0
  75. package/dist/src/lib/db-codegen/dbGenerator.d.ts +111 -0
  76. package/dist/src/lib/db-codegen/dbNaming.d.ts +45 -0
  77. package/dist/src/lib/db-codegen/dbTemplates.d.ts +97 -0
  78. package/dist/src/lib/db-codegen/dbTemplates.js +31 -10
  79. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  80. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +78 -0
  81. package/dist/src/lib/db-codegen/dbTsTypes.js +2 -2
  82. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  83. package/dist/src/lib/env-resolver.d.ts +62 -0
  84. package/dist/src/lib/fetch.d.ts +5 -0
  85. package/dist/src/lib/init-config.d.ts +46 -0
  86. package/dist/src/lib/init-config.js +7 -0
  87. package/dist/src/lib/init-config.js.map +1 -1
  88. package/dist/src/lib/migration-nag.d.ts +49 -0
  89. package/dist/src/lib/output.d.ts +49 -0
  90. package/dist/src/lib/output.js +25 -1
  91. package/dist/src/lib/output.js.map +1 -1
  92. package/dist/src/lib/paginate.d.ts +33 -0
  93. package/dist/src/lib/project-config.d.ts +97 -0
  94. package/dist/src/lib/refresh-admin-credentials.d.ts +65 -0
  95. package/dist/src/lib/resolve-platform.d.ts +45 -0
  96. package/dist/src/lib/resolve-platform.js +43 -0
  97. package/dist/src/lib/resolve-platform.js.map +1 -0
  98. package/dist/src/lib/skill-installer.d.ts +23 -0
  99. package/dist/src/lib/snapshots.d.ts +99 -0
  100. package/dist/src/lib/snapshots.js +357 -0
  101. package/dist/src/lib/snapshots.js.map +1 -0
  102. package/dist/src/lib/sync-paths.d.ts +72 -0
  103. package/dist/src/lib/sync-paths.js +29 -1
  104. package/dist/src/lib/sync-paths.js.map +1 -1
  105. package/dist/src/lib/template.d.ts +93 -0
  106. package/dist/src/lib/token-inject.d.ts +56 -0
  107. package/dist/src/lib/token-inject.js +204 -0
  108. package/dist/src/lib/token-inject.js.map +1 -0
  109. package/dist/src/lib/toml-database-config.d.ts +132 -0
  110. package/dist/src/lib/toml-params-validator.d.ts +95 -0
  111. package/dist/src/lib/version-check.d.ts +10 -0
  112. package/dist/src/lib/workflow-fragments.d.ts +41 -0
  113. package/dist/src/lib/workflow-toml-validator.d.ts +86 -0
  114. package/dist/src/lib/workflow-toml-validator.js +31 -1
  115. package/dist/src/lib/workflow-toml-validator.js.map +1 -1
  116. package/dist/src/types/index.d.ts +513 -0
  117. package/dist/src/validators.d.ts +64 -0
  118. package/dist/src/validators.js +63 -0
  119. package/dist/src/validators.js.map +1 -0
  120. 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
- function assertSafePathComponent(value, label) {
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,SAAS,uBAAuB,CAAC,KAAa,EAAE,KAAa;IAC3D,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"}
1
+ {"version":3,"file":"sync-paths.js","sourceRoot":"","sources":["../../../src/lib/sync-paths.ts"],"names":[],"mappings":"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"}
@@ -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>;