primitive-admin 1.1.0-alpha.73 → 1.1.0-alpha.75
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 +18 -25
- package/assets/skill/skills/primitive-platform/SKILL.md +30 -14
- package/dist/bin/primitive.js +25 -21
- package/dist/bin/primitive.js.map +1 -1
- package/dist/src/commands/analytics.js +117 -2
- package/dist/src/commands/analytics.js.map +1 -1
- package/dist/src/commands/auth.js +13 -107
- package/dist/src/commands/auth.js.map +1 -1
- package/dist/src/commands/collection-type-configs.js +1 -1
- package/dist/src/commands/collection-type-configs.js.map +1 -1
- package/dist/src/commands/config.js +8 -4
- package/dist/src/commands/config.js.map +1 -1
- package/dist/src/commands/database-type-configs.js +1 -1
- package/dist/src/commands/database-type-configs.js.map +1 -1
- package/dist/src/commands/databases.js +25 -31
- package/dist/src/commands/databases.js.map +1 -1
- package/dist/src/commands/documents.js +758 -53
- package/dist/src/commands/documents.js.map +1 -1
- package/dist/src/commands/env.d.ts +1 -1
- package/dist/src/commands/env.js +57 -16
- package/dist/src/commands/env.js.map +1 -1
- package/dist/src/commands/functions.d.ts +20 -0
- package/dist/src/commands/functions.js +463 -0
- package/dist/src/commands/functions.js.map +1 -0
- package/dist/src/commands/groups.js +1 -1
- package/dist/src/commands/groups.js.map +1 -1
- package/dist/src/commands/init.d.ts +12 -15
- package/dist/src/commands/init.js +38 -92
- package/dist/src/commands/init.js.map +1 -1
- package/dist/src/commands/integrations.js +5 -3
- package/dist/src/commands/integrations.js.map +1 -1
- package/dist/src/commands/prompts.js +5 -7
- package/dist/src/commands/prompts.js.map +1 -1
- package/dist/src/commands/rule-sets.js +1 -1
- package/dist/src/commands/rule-sets.js.map +1 -1
- package/dist/src/commands/scripts.js +23 -11
- package/dist/src/commands/scripts.js.map +1 -1
- package/dist/src/commands/sync-app-settings.js +2 -4
- package/dist/src/commands/sync-app-settings.js.map +1 -1
- package/dist/src/commands/sync.d.ts +65 -2
- package/dist/src/commands/sync.js +867 -21
- package/dist/src/commands/sync.js.map +1 -1
- package/dist/src/commands/workflows.js +6 -8
- package/dist/src/commands/workflows.js.map +1 -1
- package/dist/src/lib/api-client.d.ts +163 -2
- package/dist/src/lib/api-client.js +227 -5
- package/dist/src/lib/api-client.js.map +1 -1
- package/dist/src/lib/app-match-guard.d.ts +44 -0
- package/dist/src/lib/app-match-guard.js +72 -0
- package/dist/src/lib/app-match-guard.js.map +1 -0
- package/dist/src/lib/app-settings-descriptor.js +4 -0
- package/dist/src/lib/app-settings-descriptor.js.map +1 -1
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +11 -8
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +112 -60
- package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -1
- package/dist/src/lib/config-object-descriptor.js +39 -0
- package/dist/src/lib/config-object-descriptor.js.map +1 -1
- package/dist/src/lib/config-surface.d.ts +10 -0
- package/dist/src/lib/config-surface.js +17 -0
- package/dist/src/lib/config-surface.js.map +1 -1
- package/dist/src/lib/config.d.ts +15 -4
- package/dist/src/lib/config.js +22 -39
- package/dist/src/lib/config.js.map +1 -1
- package/dist/src/lib/credentials-store.d.ts +7 -15
- package/dist/src/lib/credentials-store.js +52 -85
- package/dist/src/lib/credentials-store.js.map +1 -1
- package/dist/src/lib/document-export-permissions.d.ts +30 -0
- package/dist/src/lib/document-export-permissions.js +54 -0
- package/dist/src/lib/document-export-permissions.js.map +1 -0
- package/dist/src/lib/env-resolver-core.d.ts +64 -16
- package/dist/src/lib/env-resolver-core.js +128 -17
- package/dist/src/lib/env-resolver-core.js.map +1 -1
- package/dist/src/lib/env-resolver.d.ts +3 -3
- package/dist/src/lib/env-resolver.js +2 -2
- package/dist/src/lib/function-bundle.d.ts +141 -0
- package/dist/src/lib/function-bundle.js +333 -0
- package/dist/src/lib/function-bundle.js.map +1 -0
- package/dist/src/lib/function-collect.d.ts +101 -0
- package/dist/src/lib/function-collect.js +339 -0
- package/dist/src/lib/function-collect.js.map +1 -0
- package/dist/src/lib/function-db-types.d.ts +149 -0
- package/dist/src/lib/function-db-types.js +587 -0
- package/dist/src/lib/function-db-types.js.map +1 -0
- package/dist/src/lib/function-grants-preflight.d.ts +118 -0
- package/dist/src/lib/function-grants-preflight.js +213 -0
- package/dist/src/lib/function-grants-preflight.js.map +1 -0
- package/dist/src/lib/function-sync.d.ts +191 -0
- package/dist/src/lib/function-sync.js +474 -0
- package/dist/src/lib/function-sync.js.map +1 -0
- package/dist/src/lib/function-triggers.d.ts +54 -0
- package/dist/src/lib/function-triggers.js +223 -0
- package/dist/src/lib/function-triggers.js.map +1 -0
- package/dist/src/lib/generated-config-surfaces.d.ts +389 -0
- package/dist/src/lib/generated-config-surfaces.js +1030 -2
- package/dist/src/lib/generated-config-surfaces.js.map +1 -1
- package/dist/src/lib/generated-sdk-types.d.ts +12 -0
- package/dist/src/lib/generated-sdk-types.js +13 -0
- package/dist/src/lib/generated-sdk-types.js.map +1 -0
- package/dist/src/lib/init-adopt.js +2 -2
- package/dist/src/lib/init-adopt.js.map +1 -1
- package/dist/src/lib/init-assets.js +3 -3
- package/dist/src/lib/init-config.d.ts +1 -1
- package/dist/src/lib/init-ios-links.d.ts +6 -5
- package/dist/src/lib/init-ios-links.js +9 -8
- package/dist/src/lib/init-ios-links.js.map +1 -1
- package/dist/src/lib/init-plan.d.ts +1 -1
- package/dist/src/lib/init-plan.js +1 -1
- package/dist/src/lib/init-xcode.d.ts +6 -5
- package/dist/src/lib/init-xcode.js +34 -10
- package/dist/src/lib/init-xcode.js.map +1 -1
- package/dist/src/lib/ios-app-id.d.ts +34 -0
- package/dist/src/lib/ios-app-id.js +69 -0
- package/dist/src/lib/ios-app-id.js.map +1 -0
- package/dist/src/lib/local-state.d.ts +1 -1
- package/dist/src/lib/local-state.js +1 -1
- package/dist/src/lib/local-test-cases.d.ts +1 -1
- package/dist/src/lib/local-test-cases.js +2 -1
- package/dist/src/lib/local-test-cases.js.map +1 -1
- package/dist/src/lib/migration-nag.d.ts +2 -2
- package/dist/src/lib/migration-nag.js +4 -3
- package/dist/src/lib/migration-nag.js.map +1 -1
- package/dist/src/lib/project-config.d.ts +38 -21
- package/dist/src/lib/project-config.js +57 -47
- package/dist/src/lib/project-config.js.map +1 -1
- package/dist/src/lib/snapshots.d.ts +2 -2
- package/dist/src/lib/snapshots.js +2 -2
- package/dist/src/lib/swift-codegen/generator.d.ts +1 -1
- package/dist/src/lib/swift-codegen/generator.js +1 -1
- package/dist/src/lib/sync-dir-selector.d.ts +1 -1
- package/dist/src/lib/sync-dir-selector.js +1 -1
- package/dist/src/lib/sync-paths.d.ts +52 -16
- package/dist/src/lib/sync-paths.js +68 -22
- package/dist/src/lib/sync-paths.js.map +1 -1
- package/dist/src/lib/sync-resource-types.d.ts +23 -4
- package/dist/src/lib/sync-resource-types.js +106 -8
- package/dist/src/lib/sync-resource-types.js.map +1 -1
- package/dist/src/lib/template.d.ts +2 -2
- package/dist/src/lib/template.js +2 -2
- package/dist/src/lib/test-case-keys.d.ts +1 -1
- package/dist/src/lib/test-case-keys.js +1 -1
- package/dist/src/lib/test-case-variables.d.ts +14 -0
- package/dist/src/lib/test-case-variables.js +42 -0
- package/dist/src/lib/test-case-variables.js.map +1 -1
- package/dist/src/lib/workflow-codegen/generator.d.ts +1 -1
- package/dist/src/lib/workflow-codegen/generator.js +1 -1
- package/dist/src/lib/workflow-usage.d.ts +198 -0
- package/dist/src/lib/workflow-usage.js +312 -0
- package/dist/src/lib/workflow-usage.js.map +1 -0
- package/dist/src/types/index.d.ts +12 -2
- package/package.json +4 -3
|
@@ -1,39 +1,71 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sync data path resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The COMMITTED tree lives at
|
|
5
5
|
*
|
|
6
|
-
* <projectRoot
|
|
6
|
+
* <projectRoot>/primitive/<env>/
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* — one directory per environment, and nothing else in the path. There is no
|
|
9
|
+
* app segment (#3153): since #3152 an environment names exactly one app, so
|
|
10
|
+
* the segment only duplicated what the environment already said, and it made
|
|
11
|
+
* the path unreadable. Per-environment partitioning is what issue #373 was
|
|
12
|
+
* actually about, and it is unchanged: push to dev and then to prod still see
|
|
13
|
+
* independent "last synced" pointers.
|
|
14
|
+
*
|
|
15
|
+
* MACHINE-LOCAL state stays hidden, under `<projectRoot>/.primitive/`. Snapshot
|
|
16
|
+
* backups keep BOTH partitions there — `<env>/<appId>/` — because an
|
|
17
|
+
* environment can be rebound to a different app, and a snapshot taken against
|
|
18
|
+
* the former one must not be restorable through the new one (#578, #373).
|
|
12
19
|
*
|
|
13
20
|
* When the user still passes `--dir <path>` (the legacy flag) we honor
|
|
14
21
|
* it and store sync state inside that directory, same as before. This
|
|
15
22
|
* keeps pre-project-config workflows working.
|
|
16
23
|
*/
|
|
24
|
+
/** The sync-state baseline, committed beside the environment's TOML (#3153). */
|
|
25
|
+
export declare const SYNC_STATE_FILENAME = ".sync-state.json";
|
|
26
|
+
/** Its pre-#3153 name. Detected so a missed rename is loud, never read. */
|
|
27
|
+
export declare const LEGACY_SYNC_STATE_FILENAME = ".primitive-sync.json";
|
|
17
28
|
export interface ResolveSyncDirOptions {
|
|
18
|
-
/** The resolved app ID for this sync operation. */
|
|
19
|
-
appId: string;
|
|
20
29
|
/**
|
|
21
30
|
* If the user passed `--dir <path>` on the command line, forward it
|
|
22
31
|
* here. Explicit user input always wins.
|
|
23
32
|
*/
|
|
24
33
|
userDir?: string;
|
|
25
34
|
}
|
|
35
|
+
export interface ResolveSnapshotsRootOptions extends ResolveSyncDirOptions {
|
|
36
|
+
/** The resolved app ID this snapshot slot belongs to. */
|
|
37
|
+
appId: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The sync-state baseline for a config directory.
|
|
41
|
+
*
|
|
42
|
+
* Refuses — with the same migration message every other reader gives — a
|
|
43
|
+
* directory that still holds the pre-#3153 filename and not the new one. A
|
|
44
|
+
* silently un-renamed baseline would read as "nothing has ever been synced",
|
|
45
|
+
* which turns the first `config diff` after a migration into a full-tree diff
|
|
46
|
+
* and the first push into a re-create of everything.
|
|
47
|
+
*/
|
|
48
|
+
export declare function syncStatePath(configDir: string): string;
|
|
49
|
+
/**
|
|
50
|
+
* The same refusal as a preflight: asked at the TOP of a tree command, before
|
|
51
|
+
* a request is sent, a snapshot is copied, or a directory is created.
|
|
52
|
+
*
|
|
53
|
+
* `syncStatePath` alone refuses too late for that — a pull reaches it only
|
|
54
|
+
* after fetching the whole server export, backing the tree up (legacy baseline
|
|
55
|
+
* and all) and scaffolding the directories it is about to write. The refusal
|
|
56
|
+
* has to cost nothing in either tree, so it runs before any of that.
|
|
57
|
+
*/
|
|
58
|
+
export declare function assertSyncStateLayout(configDir: string): void;
|
|
26
59
|
/**
|
|
27
60
|
* Returns the directory that should hold TOML files + sync state for the
|
|
28
61
|
* current sync operation.
|
|
29
62
|
*
|
|
30
63
|
* Resolution order:
|
|
31
64
|
* 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>
|
|
65
|
+
* 2. If we're in project mode, return <projectRoot>/primitive/<env>
|
|
34
66
|
* 3. Otherwise fall back to "./config" (the old default).
|
|
35
67
|
*/
|
|
36
|
-
export declare function resolveSyncDir(options
|
|
68
|
+
export declare function resolveSyncDir(options?: ResolveSyncDirOptions): string;
|
|
37
69
|
/**
|
|
38
70
|
* Returns the snapshot-backups root for the slot a given sync operation
|
|
39
71
|
* belongs to (issue #578, Phase 1). Snapshots reuse the SAME resolution chain
|
|
@@ -45,11 +77,15 @@ export declare function resolveSyncDir(options: ResolveSyncDirOptions): string;
|
|
|
45
77
|
* 1. If userDir (legacy `--dir`) is set, snapshots live in a sibling
|
|
46
78
|
* `<userDir>/.snapshots/` so revert works without a project root
|
|
47
79
|
* (Fork 4 — designed explicitly).
|
|
48
|
-
* 2. In project mode, the
|
|
49
|
-
* `<projectRoot>/.primitive/
|
|
80
|
+
* 2. In project mode, the machine-local
|
|
81
|
+
* `<projectRoot>/.primitive/backups/<env>/<appId>/`. The app partition
|
|
82
|
+
* survives the tree's loss of it (#3153): backups are not committed, so
|
|
83
|
+
* the no-app-ID rule does not govern them, and the partition is what
|
|
84
|
+
* stops `config revert` restoring a former app's snapshot after an
|
|
85
|
+
* environment is rebound.
|
|
50
86
|
* 3. Otherwise (bare `./config` legacy default), `./config-snapshots`.
|
|
51
87
|
*/
|
|
52
|
-
export declare function resolveSnapshotsRoot(options:
|
|
88
|
+
export declare function resolveSnapshotsRoot(options: ResolveSnapshotsRootOptions): string;
|
|
53
89
|
export declare function assertSafePathComponent(value: string, label: string): void;
|
|
54
90
|
/**
|
|
55
91
|
* Returns true if the path we'd use for sync data is the auto-resolved
|
|
@@ -60,7 +96,7 @@ export declare function isAutoResolvedSyncDir(userDir: string | undefined): bool
|
|
|
60
96
|
* Ensures the sync directory exists, creating it (and parents) if
|
|
61
97
|
* necessary. Returns the path.
|
|
62
98
|
*/
|
|
63
|
-
export declare function ensureSyncDir(options
|
|
99
|
+
export declare function ensureSyncDir(options?: ResolveSyncDirOptions): string;
|
|
64
100
|
/**
|
|
65
101
|
* Checks whether an old-style sync state file exists at `./config/.primitive-sync.json`
|
|
66
102
|
* (the pre-project-config default path). Returns a warning message if found and
|
|
@@ -96,7 +132,7 @@ export interface LegacyDatabaseTypesDirNotice {
|
|
|
96
132
|
* managed type's file would read as deleted.
|
|
97
133
|
*
|
|
98
134
|
* Both messages render RESOLVED paths. The config directory defaults to
|
|
99
|
-
* `<projectRoot
|
|
135
|
+
* `<projectRoot>/primitive/<env>/` and the CLI is invoked from
|
|
100
136
|
* the project root, so a bare `git mv database-types database-type-configs`
|
|
101
137
|
* would fail there — and fail differently again under `--dir`.
|
|
102
138
|
*
|
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Sync data path resolution.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* The COMMITTED tree lives at
|
|
5
5
|
*
|
|
6
|
-
* <projectRoot
|
|
6
|
+
* <projectRoot>/primitive/<env>/
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* — one directory per environment, and nothing else in the path. There is no
|
|
9
|
+
* app segment (#3153): since #3152 an environment names exactly one app, so
|
|
10
|
+
* the segment only duplicated what the environment already said, and it made
|
|
11
|
+
* the path unreadable. Per-environment partitioning is what issue #373 was
|
|
12
|
+
* actually about, and it is unchanged: push to dev and then to prod still see
|
|
13
|
+
* independent "last synced" pointers.
|
|
14
|
+
*
|
|
15
|
+
* MACHINE-LOCAL state stays hidden, under `<projectRoot>/.primitive/`. Snapshot
|
|
16
|
+
* backups keep BOTH partitions there — `<env>/<appId>/` — because an
|
|
17
|
+
* environment can be rebound to a different app, and a snapshot taken against
|
|
18
|
+
* the former one must not be restorable through the new one (#578, #373).
|
|
12
19
|
*
|
|
13
20
|
* When the user still passes `--dir <path>` (the legacy flag) we honor
|
|
14
21
|
* it and store sync state inside that directory, same as before. This
|
|
@@ -17,27 +24,61 @@
|
|
|
17
24
|
import { existsSync, mkdirSync } from "fs";
|
|
18
25
|
import { join } from "path";
|
|
19
26
|
import { getCurrentEnvironment } from "./env-resolver.js";
|
|
27
|
+
import { PrimitiveEnvError, PROJECT_LOCAL_STATE_DIR, PROJECT_TREE_DIR, staleLayoutMessage, } from "./env-resolver-core.js";
|
|
28
|
+
/** The sync-state baseline, committed beside the environment's TOML (#3153). */
|
|
29
|
+
export const SYNC_STATE_FILENAME = ".sync-state.json";
|
|
30
|
+
/** Its pre-#3153 name. Detected so a missed rename is loud, never read. */
|
|
31
|
+
export const LEGACY_SYNC_STATE_FILENAME = ".primitive-sync.json";
|
|
32
|
+
/**
|
|
33
|
+
* The sync-state baseline for a config directory.
|
|
34
|
+
*
|
|
35
|
+
* Refuses — with the same migration message every other reader gives — a
|
|
36
|
+
* directory that still holds the pre-#3153 filename and not the new one. A
|
|
37
|
+
* silently un-renamed baseline would read as "nothing has ever been synced",
|
|
38
|
+
* which turns the first `config diff` after a migration into a full-tree diff
|
|
39
|
+
* and the first push into a re-create of everything.
|
|
40
|
+
*/
|
|
41
|
+
export function syncStatePath(configDir) {
|
|
42
|
+
const current = join(configDir, SYNC_STATE_FILENAME);
|
|
43
|
+
if (!existsSync(current)) {
|
|
44
|
+
const legacy = join(configDir, LEGACY_SYNC_STATE_FILENAME);
|
|
45
|
+
if (existsSync(legacy)) {
|
|
46
|
+
throw new PrimitiveEnvError("stale-layout", staleLayoutMessage(`${legacy} has not been renamed to ${SYNC_STATE_FILENAME}.`), legacy);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The same refusal as a preflight: asked at the TOP of a tree command, before
|
|
53
|
+
* a request is sent, a snapshot is copied, or a directory is created.
|
|
54
|
+
*
|
|
55
|
+
* `syncStatePath` alone refuses too late for that — a pull reaches it only
|
|
56
|
+
* after fetching the whole server export, backing the tree up (legacy baseline
|
|
57
|
+
* and all) and scaffolding the directories it is about to write. The refusal
|
|
58
|
+
* has to cost nothing in either tree, so it runs before any of that.
|
|
59
|
+
*/
|
|
60
|
+
export function assertSyncStateLayout(configDir) {
|
|
61
|
+
syncStatePath(configDir);
|
|
62
|
+
}
|
|
20
63
|
/**
|
|
21
64
|
* Returns the directory that should hold TOML files + sync state for the
|
|
22
65
|
* current sync operation.
|
|
23
66
|
*
|
|
24
67
|
* Resolution order:
|
|
25
68
|
* 1. If userDir is set, return it as-is (legacy behavior).
|
|
26
|
-
* 2. If we're in project mode, return
|
|
27
|
-
* <projectRoot>/.primitive/sync/<env>/<appId>
|
|
69
|
+
* 2. If we're in project mode, return <projectRoot>/primitive/<env>
|
|
28
70
|
* 3. Otherwise fall back to "./config" (the old default).
|
|
29
71
|
*/
|
|
30
|
-
export function resolveSyncDir(options) {
|
|
72
|
+
export function resolveSyncDir(options = {}) {
|
|
31
73
|
if (options.userDir)
|
|
32
74
|
return options.userDir;
|
|
33
75
|
const env = safeEnv();
|
|
34
76
|
if (env) {
|
|
35
|
-
// Defence-in-depth:
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return join(env.projectRoot, ".primitive", "sync", env.name, options.appId);
|
|
77
|
+
// Defence-in-depth: the environment name comes from a hand-edited config
|
|
78
|
+
// file and is the only variable segment left in the tree path, so reject
|
|
79
|
+
// separators and `..` before joining it into <projectRoot>/primitive/...
|
|
80
|
+
assertSafePathComponent(env.name, "environment name");
|
|
81
|
+
return join(env.projectRoot, PROJECT_TREE_DIR, env.name);
|
|
41
82
|
}
|
|
42
83
|
return "./config";
|
|
43
84
|
}
|
|
@@ -52,8 +93,12 @@ export function resolveSyncDir(options) {
|
|
|
52
93
|
* 1. If userDir (legacy `--dir`) is set, snapshots live in a sibling
|
|
53
94
|
* `<userDir>/.snapshots/` so revert works without a project root
|
|
54
95
|
* (Fork 4 — designed explicitly).
|
|
55
|
-
* 2. In project mode, the
|
|
56
|
-
* `<projectRoot>/.primitive/
|
|
96
|
+
* 2. In project mode, the machine-local
|
|
97
|
+
* `<projectRoot>/.primitive/backups/<env>/<appId>/`. The app partition
|
|
98
|
+
* survives the tree's loss of it (#3153): backups are not committed, so
|
|
99
|
+
* the no-app-ID rule does not govern them, and the partition is what
|
|
100
|
+
* stops `config revert` restoring a former app's snapshot after an
|
|
101
|
+
* environment is rebound.
|
|
57
102
|
* 3. Otherwise (bare `./config` legacy default), `./config-snapshots`.
|
|
58
103
|
*/
|
|
59
104
|
export function resolveSnapshotsRoot(options) {
|
|
@@ -64,8 +109,9 @@ export function resolveSnapshotsRoot(options) {
|
|
|
64
109
|
if (env) {
|
|
65
110
|
// Same defence-in-depth as resolveSyncDir: never trust appId blindly when
|
|
66
111
|
// building a file-system path.
|
|
112
|
+
assertSafePathComponent(env.name, "environment name");
|
|
67
113
|
assertSafePathComponent(options.appId, "appId");
|
|
68
|
-
return join(env.projectRoot,
|
|
114
|
+
return join(env.projectRoot, PROJECT_LOCAL_STATE_DIR, "backups", env.name, options.appId);
|
|
69
115
|
}
|
|
70
116
|
return "./config-snapshots";
|
|
71
117
|
}
|
|
@@ -92,7 +138,7 @@ export function isAutoResolvedSyncDir(userDir) {
|
|
|
92
138
|
* Ensures the sync directory exists, creating it (and parents) if
|
|
93
139
|
* necessary. Returns the path.
|
|
94
140
|
*/
|
|
95
|
-
export function ensureSyncDir(options) {
|
|
141
|
+
export function ensureSyncDir(options = {}) {
|
|
96
142
|
const dir = resolveSyncDir(options);
|
|
97
143
|
if (!existsSync(dir)) {
|
|
98
144
|
mkdirSync(dir, { recursive: true });
|
|
@@ -116,14 +162,14 @@ function safeEnv() {
|
|
|
116
162
|
* their old sync data won't be picked up automatically.
|
|
117
163
|
*/
|
|
118
164
|
export function checkLegacySyncMigration(currentSyncDir) {
|
|
119
|
-
const legacySync = join(".", "config",
|
|
165
|
+
const legacySync = join(".", "config", LEGACY_SYNC_STATE_FILENAME);
|
|
120
166
|
if (!existsSync(legacySync))
|
|
121
167
|
return null;
|
|
122
168
|
// Only warn if the resolved dir is different from the legacy default.
|
|
123
169
|
const legacyDir = join(".", "config");
|
|
124
170
|
if (currentSyncDir === legacyDir)
|
|
125
171
|
return null;
|
|
126
|
-
return (`Detected old sync state at ./config
|
|
172
|
+
return (`Detected old sync state at ./config/${LEGACY_SYNC_STATE_FILENAME}. ` +
|
|
127
173
|
`The config directory now lives at ${currentSyncDir}. ` +
|
|
128
174
|
`Run 'primitive config pull' to re-sync into the new location.`);
|
|
129
175
|
}
|
|
@@ -154,7 +200,7 @@ function shellQuote(path) {
|
|
|
154
200
|
* managed type's file would read as deleted.
|
|
155
201
|
*
|
|
156
202
|
* Both messages render RESOLVED paths. The config directory defaults to
|
|
157
|
-
* `<projectRoot
|
|
203
|
+
* `<projectRoot>/primitive/<env>/` and the CLI is invoked from
|
|
158
204
|
* the project root, so a bare `git mv database-types database-type-configs`
|
|
159
205
|
* would fail there — and fail differently again under `--dir`.
|
|
160
206
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync-paths.js","sourceRoot":"","sources":["../../../src/lib/sync-paths.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"sync-paths.js","sourceRoot":"","sources":["../../../src/lib/sync-paths.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* into a sync slot (issue #1659).
|
|
4
4
|
*
|
|
5
5
|
* `config pull` writes one file per server entity and records what it wrote in
|
|
6
|
-
* `.
|
|
6
|
+
* `.sync-state.json` (`SyncState.entities[<slot>]`). Until #1659 it never
|
|
7
7
|
* removed a local file whose entity had disappeared server-side, so the next
|
|
8
8
|
* `config push` — which treats local TOML as the source of truth — silently
|
|
9
9
|
* re-created the just-deleted entity.
|
|
@@ -122,7 +122,7 @@ export declare function attemptFetch<T>(fn: () => Promise<T>): Promise<{
|
|
|
122
122
|
}>;
|
|
123
123
|
/**
|
|
124
124
|
* A filename must stay inside its type's directory. State is read from a file
|
|
125
|
-
* on disk, so a corrupt or hand-edited `.
|
|
125
|
+
* on disk, so a corrupt or hand-edited `.sync-state.json` must never be
|
|
126
126
|
* able to steer an `unlinkSync` outside the sync slot.
|
|
127
127
|
*/
|
|
128
128
|
export declare function isSafeSyncFilename(filename: string): boolean;
|
|
@@ -264,8 +264,10 @@ export interface PruneResult {
|
|
|
264
264
|
* sidecar `<key>.tests/` directory.
|
|
265
265
|
*
|
|
266
266
|
* A file with uncommitted git edits is kept and reported instead: the operator
|
|
267
|
-
* may be mid-way through authoring a change they meant to push
|
|
268
|
-
*
|
|
267
|
+
* may be mid-way through authoring a change they meant to push — and for an
|
|
268
|
+
* entity that owns source files beyond its TOML (#3179), an edit to any of them
|
|
269
|
+
* keeps the whole entity. Everything removed here is recoverable from the
|
|
270
|
+
* pre-pull snapshot via `config revert`.
|
|
269
271
|
*/
|
|
270
272
|
export declare function applyPrune(options: {
|
|
271
273
|
configDir: string;
|
|
@@ -276,6 +278,23 @@ export declare function applyPrune(options: {
|
|
|
276
278
|
blockType?: string;
|
|
277
279
|
blockKey?: string;
|
|
278
280
|
}>;
|
|
281
|
+
/**
|
|
282
|
+
* Further tree-relative files this entity owns, from the prior sync state
|
|
283
|
+
* (#3179). A server function is its `functions/<key>.toml` PLUS the
|
|
284
|
+
* TypeScript its `entry` reaches, which lives at paths only the state knows —
|
|
285
|
+
* so removing the file alone would leave orphaned code that the next push
|
|
286
|
+
* would try to capture again. Returned paths are confined to the config tree;
|
|
287
|
+
* anything else is ignored rather than followed.
|
|
288
|
+
*/
|
|
289
|
+
extraFilesForKey?: (key: string) => string[];
|
|
290
|
+
/**
|
|
291
|
+
* Tree-relative paths that SURVIVE this prune because another entity still
|
|
292
|
+
* owns them (#3179). Two functions may import the same module, and the
|
|
293
|
+
* pulled sources of both list it; deleting it with one would leave the other
|
|
294
|
+
* unbuildable while its sync state still called it current. A shared path is
|
|
295
|
+
* left alone and reported.
|
|
296
|
+
*/
|
|
297
|
+
retainedFiles?: Set<string>;
|
|
279
298
|
logger?: (msg: string) => void;
|
|
280
299
|
}): Promise<PruneResult>;
|
|
281
300
|
/** Address a prior-state entry for detail-fetch and delete. */
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* into a sync slot (issue #1659).
|
|
4
4
|
*
|
|
5
5
|
* `config pull` writes one file per server entity and records what it wrote in
|
|
6
|
-
* `.
|
|
6
|
+
* `.sync-state.json` (`SyncState.entities[<slot>]`). Until #1659 it never
|
|
7
7
|
* removed a local file whose entity had disappeared server-side, so the next
|
|
8
8
|
* `config push` — which treats local TOML as the source of truth — silently
|
|
9
9
|
* re-created the just-deleted entity.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* test-directory block type. The pull write loops deliberately stay as they are
|
|
14
14
|
* — this table is layout metadata, not a write framework.
|
|
15
15
|
*/
|
|
16
|
-
import { existsSync, readFileSync, readdirSync, rmSync, statSync, unlinkSync } from "fs";
|
|
16
|
+
import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync, rmSync, statSync, unlinkSync, } from "fs";
|
|
17
17
|
import { basename, dirname, join, resolve, sep } from "path";
|
|
18
18
|
import { parseConfigToml } from "./config-toml.js";
|
|
19
19
|
const tomlFile = (key) => `${key}.toml`;
|
|
@@ -104,6 +104,21 @@ export const SYNC_RESOURCE_TYPES = [
|
|
|
104
104
|
// Server drains `scriptsByApp` across all pages (#1668).
|
|
105
105
|
listingComplete: true,
|
|
106
106
|
},
|
|
107
|
+
{
|
|
108
|
+
// #3179 — server functions. `functions/<key>.toml` states the function and
|
|
109
|
+
// names its `entry`; the TypeScript the entry reaches lives beside it in
|
|
110
|
+
// the same tree and is captured into the version envelope on push, so the
|
|
111
|
+
// entity spans more than its one file. Prune removes the TOML; the sources
|
|
112
|
+
// it pulled are tracked in the state slot and removed with it.
|
|
113
|
+
label: "function",
|
|
114
|
+
dir: "functions",
|
|
115
|
+
ext: ".toml",
|
|
116
|
+
stateSlot: "functions",
|
|
117
|
+
fileFromKey: tomlFile,
|
|
118
|
+
// Server drains `functionsByApp` across all pages (#1668's rule, applied at
|
|
119
|
+
// birth in `server-functions-controller.ts`).
|
|
120
|
+
listingComplete: true,
|
|
121
|
+
},
|
|
107
122
|
{
|
|
108
123
|
label: "database-type-config",
|
|
109
124
|
dir: "database-type-configs",
|
|
@@ -180,7 +195,7 @@ export async function attemptFetch(fn) {
|
|
|
180
195
|
}
|
|
181
196
|
/**
|
|
182
197
|
* A filename must stay inside its type's directory. State is read from a file
|
|
183
|
-
* on disk, so a corrupt or hand-edited `.
|
|
198
|
+
* on disk, so a corrupt or hand-edited `.sync-state.json` must never be
|
|
184
199
|
* able to steer an `unlinkSync` outside the sync slot.
|
|
185
200
|
*/
|
|
186
201
|
export function isSafeSyncFilename(filename) {
|
|
@@ -386,16 +401,37 @@ export function classifyLocalOnly(priorEntities, slot, key) {
|
|
|
386
401
|
const managed = (priorEntities?.[slot] ?? {});
|
|
387
402
|
return key in managed ? LOCAL_ONLY_ABSENT : LOCAL_ONLY_NEW;
|
|
388
403
|
}
|
|
404
|
+
/**
|
|
405
|
+
* Whether `target` is still inside `root` once symlinks are resolved.
|
|
406
|
+
*
|
|
407
|
+
* A lexical prefix check answers a question about strings; this one answers the
|
|
408
|
+
* question the `unlinkSync` actually asks. `functions/shared` pointing at an
|
|
409
|
+
* external directory passes the string test and deletes someone else's file.
|
|
410
|
+
*/
|
|
411
|
+
function realPathStaysInside(root, target) {
|
|
412
|
+
try {
|
|
413
|
+
if (lstatSync(target).isSymbolicLink())
|
|
414
|
+
return false;
|
|
415
|
+
const realRoot = realpathSync(root);
|
|
416
|
+
const real = realpathSync(target);
|
|
417
|
+
return real.startsWith(realRoot + sep);
|
|
418
|
+
}
|
|
419
|
+
catch {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
389
423
|
/**
|
|
390
424
|
* Remove the local files for `keys` (as decided by `planPrune`), along with any
|
|
391
425
|
* sidecar `<key>.tests/` directory.
|
|
392
426
|
*
|
|
393
427
|
* A file with uncommitted git edits is kept and reported instead: the operator
|
|
394
|
-
* may be mid-way through authoring a change they meant to push
|
|
395
|
-
*
|
|
428
|
+
* may be mid-way through authoring a change they meant to push — and for an
|
|
429
|
+
* entity that owns source files beyond its TOML (#3179), an edit to any of them
|
|
430
|
+
* keeps the whole entity. Everything removed here is recoverable from the
|
|
431
|
+
* pre-pull snapshot via `config revert`.
|
|
396
432
|
*/
|
|
397
433
|
export async function applyPrune(options) {
|
|
398
|
-
const { configDir, resourceType, keys, testCaseEntities, logger } = options;
|
|
434
|
+
const { configDir, resourceType, keys, testCaseEntities, extraFilesForKey, retainedFiles, logger, } = options;
|
|
399
435
|
const log = logger ?? (() => { });
|
|
400
436
|
const result = {
|
|
401
437
|
prunedKeys: [],
|
|
@@ -409,19 +445,65 @@ export async function applyPrune(options) {
|
|
|
409
445
|
const filePath = join(configDir, resourceType.dir, filename);
|
|
410
446
|
if (!existsSync(filePath))
|
|
411
447
|
continue;
|
|
448
|
+
// #3179 — the entity's own source files, recorded by the pull that wrote
|
|
449
|
+
// them. Each path is re-confined here: it came out of a JSON file on disk,
|
|
450
|
+
// so a corrupt or hand-edited sync state must never steer an `unlinkSync`
|
|
451
|
+
// out of the tree — lexically first, then against the real path, because a
|
|
452
|
+
// symlinked directory inside the tree resolves outside it.
|
|
453
|
+
const extras = [];
|
|
454
|
+
for (const extra of extraFilesForKey?.(key) ?? []) {
|
|
455
|
+
if (retainedFiles?.has(extra)) {
|
|
456
|
+
log(` Kept ${extra} — another entity still uses it`);
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const target = resolve(configDir, extra);
|
|
460
|
+
const root = resolve(configDir);
|
|
461
|
+
if (target === root || !target.startsWith(root + sep)) {
|
|
462
|
+
log(` Skipped removing '${extra}' — it resolves outside the config tree`);
|
|
463
|
+
continue;
|
|
464
|
+
}
|
|
465
|
+
if (!existsSync(target))
|
|
466
|
+
continue;
|
|
467
|
+
if (!realPathStaysInside(root, target)) {
|
|
468
|
+
log(` Skipped removing '${extra}' — a symlink takes it outside the config tree`);
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
extras.push({ relative: extra, absolute: target });
|
|
472
|
+
}
|
|
412
473
|
// Untracked does not mean edited: in a sync dir that was never committed
|
|
413
474
|
// (or in no repository at all) every pulled file is untracked, and counting
|
|
414
475
|
// that as a local edit would keep every stale file and leave #1659 unfixed.
|
|
476
|
+
//
|
|
477
|
+
// The entity's source files are asked the same question BEFORE anything is
|
|
478
|
+
// removed (#3179): a half-pruned function — TOML gone, edited TypeScript
|
|
479
|
+
// kept — is neither state the operator can act on, so an edit anywhere in
|
|
480
|
+
// the entity keeps all of it.
|
|
481
|
+
const edited = [];
|
|
415
482
|
if (await hasUncommittedChanges(filePath, { includeUntracked: false })) {
|
|
483
|
+
edited.push(relativePath);
|
|
484
|
+
}
|
|
485
|
+
for (const extra of extras) {
|
|
486
|
+
if (await hasUncommittedChanges(extra.absolute, { includeUntracked: false })) {
|
|
487
|
+
edited.push(extra.relative);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
if (edited.length > 0) {
|
|
416
491
|
result.keptWithLocalEdits.push(key);
|
|
417
|
-
log(` Kept ${relativePath} — ${ABSENT_FROM_EXPORT} but
|
|
418
|
-
"uncommitted local edits; remove it yourself or re-create the entity on the server");
|
|
492
|
+
log(` Kept ${relativePath} — ${ABSENT_FROM_EXPORT} but ${edited.join(", ")} ` +
|
|
493
|
+
"has uncommitted local edits; remove it yourself or re-create the entity on the server");
|
|
419
494
|
continue;
|
|
420
495
|
}
|
|
421
496
|
unlinkSync(filePath);
|
|
422
497
|
result.prunedKeys.push(key);
|
|
423
498
|
result.removed.push(relativePath);
|
|
424
499
|
log(` Removed ${relativePath} — ${ABSENT_FROM_EXPORT}`);
|
|
500
|
+
for (const extra of extras) {
|
|
501
|
+
if (!existsSync(extra.absolute))
|
|
502
|
+
continue;
|
|
503
|
+
unlinkSync(extra.absolute);
|
|
504
|
+
result.removed.push(extra.relative);
|
|
505
|
+
log(` Removed ${extra.relative} — ${ABSENT_FROM_EXPORT}`);
|
|
506
|
+
}
|
|
425
507
|
// #2645 — the entity's named config bodies go with it. They describe
|
|
426
508
|
// configurations of a block the server no longer has, and leaving them
|
|
427
509
|
// would have the next push re-create the block from its own leftovers.
|
|
@@ -673,6 +755,22 @@ export const PUSH_PRUNE_SPECS = [
|
|
|
673
755
|
await c.deletePrompt(a, id, true);
|
|
674
756
|
},
|
|
675
757
|
},
|
|
758
|
+
{
|
|
759
|
+
// #3179 — server functions. Hard-delete rather than archive, following the
|
|
760
|
+
// same reasoning as every sibling: an archived function keeps holding its
|
|
761
|
+
// key in the app's SINGLE key namespace, which now blocks not just another
|
|
762
|
+
// function but a workflow, a script or a webhook wanting that key, with no
|
|
763
|
+
// way to reclaim it from `config`. The hard delete is also what destroys
|
|
764
|
+
// the versions' R2 objects, so a pruned function leaves no bundles behind.
|
|
765
|
+
resourceType: pruneTypeFor("function"),
|
|
766
|
+
keyFromFile: tomlDeclaredKeyFromFile("function", "key"),
|
|
767
|
+
addressOf: addressById,
|
|
768
|
+
fetchDetail: (c, a, id) => c.getFunction(a, id),
|
|
769
|
+
modifiedAtOf: pruneModifiedAt,
|
|
770
|
+
deleteEntity: async (c, a, id) => {
|
|
771
|
+
await c.deleteFunction(a, id, { hard: true });
|
|
772
|
+
},
|
|
773
|
+
},
|
|
676
774
|
{
|
|
677
775
|
resourceType: pruneTypeFor("workflow"),
|
|
678
776
|
keyFromFile: tomlDeclaredKeyFromFile("workflow", "key"),
|