@foir/cli 0.69.0 → 0.70.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +5861 -2811
- package/dist/lib/config-helpers.d.ts +176 -15
- package/package.json +5 -5
|
@@ -179,8 +179,17 @@ interface AccessInput {
|
|
|
179
179
|
* (other operators can't see them unless shared).
|
|
180
180
|
* - `'project'` — no individual owner; records belong to the project. The
|
|
181
181
|
* platform default.
|
|
182
|
+
* - `'organization'` — owned by one of the project's ORGANIZATIONS (the
|
|
183
|
+
* tenants of a SaaS built on this project). Ownership, not containment:
|
|
184
|
+
* the row stays in the project plane, so the project credential finds
|
|
185
|
+
* every organization's rows — which is how a multi-tenant app resolves
|
|
186
|
+
* `<sub>.theapp.com` to an org before it knows which org the request is
|
|
187
|
+
* for. Written by the project (never on behalf of a customer), owner
|
|
188
|
+
* named by `ownerId` or by the org the caller acts in (`X-Org-Id`);
|
|
189
|
+
* `singleton` means one record per organization. Refused on a project
|
|
190
|
+
* with no organizations and on a shared (layered/group) model.
|
|
182
191
|
*/
|
|
183
|
-
ownerPlane: 'customer' | 'admin' | 'project';
|
|
192
|
+
ownerPlane: 'customer' | 'admin' | 'project' | 'organization';
|
|
184
193
|
/**
|
|
185
194
|
* Visibility new records start at (the record descriptor modes):
|
|
186
195
|
* - `'private'` — only the owner and explicit grants.
|
|
@@ -285,11 +294,12 @@ interface ApplyConfigOperationInput {
|
|
|
285
294
|
/** Precondition that must be met before execution (e.g., segment membership). */
|
|
286
295
|
precondition?: Precondition;
|
|
287
296
|
/**
|
|
288
|
-
* Execution mode the operation supports. `sync` blocks the
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
* for long-running work that
|
|
297
|
+
* Execution mode the operation supports. `sync` blocks the caller on the
|
|
298
|
+
* dispatch reaching a terminal state, bounded by the platform's
|
|
299
|
+
* `SyncTimeout` of 60s. `async` dispatches with 202-ack semantics: the
|
|
300
|
+
* extension returns 202 immediately and calls back via the typed GraphQL
|
|
301
|
+
* mutation when work completes. Use `async` for long-running work that
|
|
302
|
+
* exceeds the 60s wall clock.
|
|
293
303
|
*
|
|
294
304
|
* There is only one flavour of async on the platform: the callback-based
|
|
295
305
|
* handshake. The prior "fire-and-forget" meaning of async was retired
|
|
@@ -303,9 +313,9 @@ interface ApplyConfigOperationInput {
|
|
|
303
313
|
mode?: 'sync' | 'async';
|
|
304
314
|
/**
|
|
305
315
|
* How long the platform waits for a callback before marking the execution
|
|
306
|
-
* `timed_out` and (per retry policy) re-queueing or sending to DLQ.
|
|
307
|
-
*
|
|
308
|
-
*
|
|
316
|
+
* `timed_out` and (per retry policy) re-queueing or sending to DLQ. Omit
|
|
317
|
+
* for the platform default of 24h. Clamped to [5m, 7d]. Only meaningful
|
|
318
|
+
* when `mode === 'async'`.
|
|
309
319
|
*/
|
|
310
320
|
callbackTtlSeconds?: number;
|
|
311
321
|
/**
|
|
@@ -548,7 +558,17 @@ interface ApplyConfigRelyingPartyInput {
|
|
|
548
558
|
clientId: string;
|
|
549
559
|
/** Display name shown on the hosted login. */
|
|
550
560
|
name: string;
|
|
551
|
-
/**
|
|
561
|
+
/**
|
|
562
|
+
* Allow-listed redirect URIs the platform will 302 to with the code, e.g.
|
|
563
|
+
* `['https://app.eide.clothing/auth/callback']`.
|
|
564
|
+
*
|
|
565
|
+
* Matched EXACTLY, including the path and query, so each deployment is listed
|
|
566
|
+
* in full. Wildcards are rejected rather than stored: a pattern would match
|
|
567
|
+
* only its own literal text, which reads like an allow-list entry and covers
|
|
568
|
+
* nothing. https, except on a loopback host (`http://127.0.0.1:8976/callback`)
|
|
569
|
+
* where a CLI or native app cannot hold a certificate. No fragment, no
|
|
570
|
+
* userinfo.
|
|
571
|
+
*/
|
|
552
572
|
redirectUris: string[];
|
|
553
573
|
/** OAuth scopes the RP can request (intersection with caller scopes wins). */
|
|
554
574
|
allowedScopes?: string[];
|
|
@@ -626,8 +646,27 @@ interface ApplyConfigApiKeyInput {
|
|
|
626
646
|
scopes?: string[];
|
|
627
647
|
/** Restrict the key to specific model keys (e.g. ["tilly_note", "tilly_block"]). */
|
|
628
648
|
allowedModels?: string[];
|
|
629
|
-
/** Restrict file uploads
|
|
649
|
+
/** Restrict file uploads: a MIME type ("image/png"), a whole type ("image/*"), or IMAGE, VIDEO, FILE. */
|
|
630
650
|
allowedFileTypes?: string[];
|
|
651
|
+
/**
|
|
652
|
+
* Confine the key to one ORGANISATION of this project, by id — the same value
|
|
653
|
+
* `foir api-keys create --confine-to-org` takes. An organisation is
|
|
654
|
+
* identified by a minted id, the way a project and a tenant are.
|
|
655
|
+
*
|
|
656
|
+
* A confined key reads and writes only that organisation's rows plus the
|
|
657
|
+
* project-shared tier, and the confinement travels on the credential rather
|
|
658
|
+
* than on any request, so nothing downstream can widen it.
|
|
659
|
+
*
|
|
660
|
+
* It is also part of the key's IDENTITY here, not just its configuration.
|
|
661
|
+
* Two organisations may each have a key named "Storefront", so the reconciler
|
|
662
|
+
* matches on name AND organisation; a manifest that declared one and omitted
|
|
663
|
+
* this field would be matched against the other one's key and rotate it.
|
|
664
|
+
*
|
|
665
|
+
* Set on CREATE only, like `configId` beside it: a key's organisation cannot
|
|
666
|
+
* be changed afterwards, and a manifest that moved one would be asking for a
|
|
667
|
+
* credential to change which tenant it belongs to.
|
|
668
|
+
*/
|
|
669
|
+
org?: string;
|
|
631
670
|
}
|
|
632
671
|
/**
|
|
633
672
|
* A customer RBAC role declared in foir.config.ts and reconciled by
|
|
@@ -662,6 +701,25 @@ interface ApplyConfigCustomerRoleInput {
|
|
|
662
701
|
*/
|
|
663
702
|
isDefault?: boolean;
|
|
664
703
|
}
|
|
704
|
+
/**
|
|
705
|
+
* Saying "I do not want this one" about a source or sink the manifest marks
|
|
706
|
+
* optional.
|
|
707
|
+
*
|
|
708
|
+
* EXPLICIT, BECAUSE SILENCE IS THE WRONG DEFAULT HERE. An optional source left
|
|
709
|
+
* out of the config could be a decision or an oversight, and the two have
|
|
710
|
+
* opposite consequences: one is an operator who does not want their customers
|
|
711
|
+
* synced, the other is an operator who thinks they are and is wrong. So an
|
|
712
|
+
* optional source that is neither mapped nor declined is refused at push, with
|
|
713
|
+
* both spellings named. Nothing is submitted for a declined one, so the platform
|
|
714
|
+
* drops the operations that would have carried it and grants no capability for
|
|
715
|
+
* it.
|
|
716
|
+
*
|
|
717
|
+
* sources: {
|
|
718
|
+
* product: { toModel: 'product', naturalKey: 'shopifyId', fields: { … } },
|
|
719
|
+
* customer: false, // this project does not sync customers
|
|
720
|
+
* }
|
|
721
|
+
*/
|
|
722
|
+
type AppMappingDeclined = false;
|
|
665
723
|
/**
|
|
666
724
|
* Source-type mapping entry for an app install. Maps a manifest-declared
|
|
667
725
|
* source type onto a project model. See docs/platform/apps.md §Mapping step.
|
|
@@ -698,8 +756,10 @@ interface AppInput {
|
|
|
698
756
|
/** Opaque settings forwarded to the app's middleware. */
|
|
699
757
|
settings?: Record<string, unknown>;
|
|
700
758
|
mappings?: {
|
|
701
|
-
|
|
702
|
-
|
|
759
|
+
/** `false` declines an OPTIONAL source. See AppMappingDeclined. */
|
|
760
|
+
sources?: Record<string, AppSourceMappingInput | AppMappingDeclined>;
|
|
761
|
+
/** `false` declines an OPTIONAL sink. See AppMappingDeclined. */
|
|
762
|
+
sinks?: Record<string, AppSinkMappingInput | AppMappingDeclined>;
|
|
703
763
|
placementFields?: Record<string, AppPlacementFieldChoiceInput>;
|
|
704
764
|
};
|
|
705
765
|
}
|
|
@@ -758,6 +818,38 @@ interface ApplyConfigProjectSettingsInput {
|
|
|
758
818
|
* Whether email one-time-code customer login is exposed. Default false.
|
|
759
819
|
*/
|
|
760
820
|
customerOtpEnabled?: boolean;
|
|
821
|
+
/**
|
|
822
|
+
* Whether a signed-in customer may register an organization of their own
|
|
823
|
+
* through `registerOperatorOrganization`. Default false. Needs
|
|
824
|
+
* `operatorSignupRole` to name the role the founder is given, or the
|
|
825
|
+
* platform refuses every registration.
|
|
826
|
+
*/
|
|
827
|
+
operatorSignupEnabled?: boolean;
|
|
828
|
+
/**
|
|
829
|
+
* The key of the customer role a self-registered organization's founder
|
|
830
|
+
* holds in it (a role of this project, e.g. `'operator'`). No default.
|
|
831
|
+
*/
|
|
832
|
+
operatorSignupRole?: string;
|
|
833
|
+
/**
|
|
834
|
+
* The customer roles an organization's own staffer may hand out, by key.
|
|
835
|
+
*
|
|
836
|
+
* THE CEILING ON WHAT AN ORGANIZATION CAN BECOME, set by the party that is
|
|
837
|
+
* not inside it. An organization's staffer may add a member with a role only
|
|
838
|
+
* if the PROJECT declared that role assignable, which is why this is
|
|
839
|
+
* manifest-shaped and lives under project settings rather than anywhere an
|
|
840
|
+
* organization can reach.
|
|
841
|
+
*
|
|
842
|
+
* TRI-STATE, and the empty array is the point: omit it to leave the stored
|
|
843
|
+
* list alone, give a non-empty array to set it, and give an EMPTY array to
|
|
844
|
+
* take the capability away from every organization in the project. A bare
|
|
845
|
+
* "send nothing to clear" could not tell the last two apart.
|
|
846
|
+
*
|
|
847
|
+
* Every key must name a live customer role of this project. The platform
|
|
848
|
+
* refuses the whole list rather than dropping an entry it cannot resolve,
|
|
849
|
+
* because a list that quietly lost one is a capability an operator believes
|
|
850
|
+
* they granted and did not.
|
|
851
|
+
*/
|
|
852
|
+
organizationAssignableRoles?: string[];
|
|
761
853
|
}
|
|
762
854
|
/** Self-serve signup mode, as authored in config-as-code. */
|
|
763
855
|
type SignupModeName = 'public' | 'restricted' | 'waitlist' | 'closed';
|
|
@@ -808,11 +900,68 @@ interface ApplyConfigDesignTokensInput {
|
|
|
808
900
|
/** Escape hatch for forward-compatible W3C groups (`spacing`, `border`, …). */
|
|
809
901
|
[key: string]: unknown;
|
|
810
902
|
}
|
|
903
|
+
/**
|
|
904
|
+
* A whole config manifest: every family `foir push` reconciles, plus the
|
|
905
|
+
* config's own identity and the project pin.
|
|
906
|
+
*
|
|
907
|
+
* ⚠️ NO INDEX SIGNATURE, deliberately, and it used to have one. `[key: string]:
|
|
908
|
+
* unknown` made every top-level key legal, so a block spelled `organisations`
|
|
909
|
+
* instead of `organizations` — or one from a release this CLI does not have —
|
|
910
|
+
* type-checked, serialised, was accepted by the server's equally tolerant parse
|
|
911
|
+
* and applied by nothing. Both sides agreed. Nothing errored. The block did
|
|
912
|
+
* nothing, for as long as nobody looked. Without the signature, TypeScript's
|
|
913
|
+
* excess-property check refuses the literal at the point somebody writes it,
|
|
914
|
+
* which is the cheapest place this can possibly be caught.
|
|
915
|
+
*
|
|
916
|
+
* The server reports what it did not recognise as well, because a type check is
|
|
917
|
+
* only one of the two sides and a manifest can reach it from a hand-written
|
|
918
|
+
* JSON, an older CLI, or a generator.
|
|
919
|
+
*
|
|
920
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
921
|
+
* WHAT A PUSH DOES TO ORGANISATIONS
|
|
922
|
+
*
|
|
923
|
+
* A PUSH NEVER CREATES, MOVES, SUSPENDS OR DELETES AN ORGANISATION. Stated on
|
|
924
|
+
* the server too, beside the parser that would have to grow a family for it
|
|
925
|
+
* (`organisationPushRule` in the configs handler), because a rule held in one
|
|
926
|
+
* repository is a rule the other can contradict.
|
|
927
|
+
*
|
|
928
|
+
* Organisation POLICY is manifest-shaped and belongs here: whether self-serve
|
|
929
|
+
* sign-up is on, which role a founder is given, which roles an organisation may
|
|
930
|
+
* assign. Organisation INSTANCES are administrative state, like a customer or a
|
|
931
|
+
* credential's last use — created by an operator, by a customer signing up, or
|
|
932
|
+
* by a provisioning call, and suspended or deleted by deliberate acts with
|
|
933
|
+
* their own confirmations and their own audit trail.
|
|
934
|
+
*
|
|
935
|
+
* The reason it has to be said rather than assumed is this file's own prune
|
|
936
|
+
* rule: a family a manifest OMITS is a family whose resources this config owns
|
|
937
|
+
* and no longer wants. Apply that to organisations and a founder who pushed a
|
|
938
|
+
* config without an organisations block would lose a live tenant — every row,
|
|
939
|
+
* every customer, every credential — because a file did not mention it.
|
|
940
|
+
*/
|
|
811
941
|
interface ApplyConfigInput {
|
|
812
942
|
key: string;
|
|
813
943
|
name: string;
|
|
814
944
|
configType?: string;
|
|
815
945
|
force?: boolean;
|
|
946
|
+
/**
|
|
947
|
+
* Free text on the config row, for a reader rather than for the platform.
|
|
948
|
+
* Nothing resolves against it.
|
|
949
|
+
*/
|
|
950
|
+
description?: string;
|
|
951
|
+
/**
|
|
952
|
+
* Which way content moves for this config: `'READ'` (the default),
|
|
953
|
+
* `'write'`, `'bidirectional'`. Recorded on the config row.
|
|
954
|
+
*
|
|
955
|
+
* A STRING. It was undeclared here until the manifest's top level gained a
|
|
956
|
+
* type at all, and one config in this repository had been sending the number
|
|
957
|
+
* `1` — which made the SERVER'S metadata decode fail outright, so that
|
|
958
|
+
* manifest's name, description and configType fell through to defaults on
|
|
959
|
+
* every push. One key of the wrong type, every metadata field silently
|
|
960
|
+
* dropped, nothing reported anywhere.
|
|
961
|
+
*/
|
|
962
|
+
direction?: string;
|
|
963
|
+
/** The domain a connector-shaped config talks to. Recorded on the row. */
|
|
964
|
+
connectionDomain?: string;
|
|
816
965
|
/** Base URL prepended to relative operation endpoints. */
|
|
817
966
|
operationBaseUrl?: string;
|
|
818
967
|
/**
|
|
@@ -854,7 +1003,19 @@ interface ApplyConfigInput {
|
|
|
854
1003
|
* only — see ApplyConfigEmailCopyInput for why it never prunes).
|
|
855
1004
|
*/
|
|
856
1005
|
emailCopy?: ApplyConfigEmailCopyInput[];
|
|
857
|
-
|
|
1006
|
+
/**
|
|
1007
|
+
* The project's bring-your-own transactional email sender: `fromEmail` and a
|
|
1008
|
+
* `resendKeySecretRef`. REF ONLY — the raw key is never in the committed
|
|
1009
|
+
* manifest; it is written out of band with `foir secrets put` and this
|
|
1010
|
+
* references the resulting vault ref. Omitted leaves the sender unchanged.
|
|
1011
|
+
*/
|
|
1012
|
+
projectEmail?: ApplyConfigProjectEmailInput;
|
|
1013
|
+
}
|
|
1014
|
+
/** The project's own email sender, as authored in config-as-code. */
|
|
1015
|
+
interface ApplyConfigProjectEmailInput {
|
|
1016
|
+
fromEmail?: string;
|
|
1017
|
+
/** A vault ref, never a key. `foir secrets put` mints one. */
|
|
1018
|
+
resendKeySecretRef?: string;
|
|
858
1019
|
}
|
|
859
1020
|
/** Define a complete config manifest. */
|
|
860
1021
|
declare function defineConfig(config: ApplyConfigInput): ApplyConfigInput;
|
|
@@ -927,4 +1088,4 @@ interface FoirSecretsConfig {
|
|
|
927
1088
|
*/
|
|
928
1089
|
declare function defineSecrets(config: FoirSecretsConfig): FoirSecretsConfig;
|
|
929
1090
|
|
|
930
|
-
export { type AccessInput, type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigContextDimensionInput, type ApplyConfigCustomerRoleInput, type ApplyConfigDesignTokensInput, type ApplyConfigEmailCopyInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigLocaleInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigRelyingPartyInput, type ApplyConfigRelyingPartyLoginMethods, type ApplyConfigScheduleInput, type ApplyConfigVariantCatalogEntryInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type FieldAccessInput, type FieldDefinitionInput, type FieldPlacement, type FieldRelationshipInput, type FoirSecretsConfig, type LookupDefinitionInput, type QuotaRule, type ReferenceFieldConfig, type ReferenceFieldDefinitionInput, type SecretDeclaration, type SecretOwnerKind, type SelectFieldConfig, type SelectFieldDefinitionInput, type SignupModeName, type SignupRestrictionsInput, defineAuthProvider, defineConfig, defineContextDimension, defineDesignTokens, defineEmailCopy, defineEnumField, defineField, defineHook, defineLocale, defineModel, defineOperation, definePlacement, defineRelyingParty, defineSchedule, defineSecrets, defineSelectField, defineVariantCatalogEntry };
|
|
1091
|
+
export { type AccessInput, type AppInput, type AppMappingDeclined, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigContextDimensionInput, type ApplyConfigCustomerRoleInput, type ApplyConfigDesignTokensInput, type ApplyConfigEmailCopyInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigLocaleInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectEmailInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigRelyingPartyInput, type ApplyConfigRelyingPartyLoginMethods, type ApplyConfigScheduleInput, type ApplyConfigVariantCatalogEntryInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type FieldAccessInput, type FieldDefinitionInput, type FieldPlacement, type FieldRelationshipInput, type FoirSecretsConfig, type LookupDefinitionInput, type QuotaRule, type ReferenceFieldConfig, type ReferenceFieldDefinitionInput, type SecretDeclaration, type SecretOwnerKind, type SelectFieldConfig, type SelectFieldDefinitionInput, type SignupModeName, type SignupRestrictionsInput, defineAuthProvider, defineConfig, defineContextDimension, defineDesignTokens, defineEmailCopy, defineEnumField, defineField, defineHook, defineLocale, defineModel, defineOperation, definePlacement, defineRelyingParty, defineSchedule, defineSecrets, defineSelectField, defineVariantCatalogEntry };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foir/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.70.0",
|
|
4
4
|
"description": "Universal platform CLI for Foir platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"publishConfig": {
|
|
@@ -62,10 +62,10 @@
|
|
|
62
62
|
"typescript": "5.9.2",
|
|
63
63
|
"vitest": "^3.2.4",
|
|
64
64
|
"@foir/assistant": "0.0.0",
|
|
65
|
-
"@foir/
|
|
66
|
-
"@foir/proto-ts": "0.109.0",
|
|
65
|
+
"@foir/sdk": "0.10.0",
|
|
67
66
|
"@foir/rpc-node": "0.0.0",
|
|
68
|
-
"@foir/
|
|
67
|
+
"@foir/proto-ts": "0.109.0",
|
|
68
|
+
"@foir/console-routes": "0.0.0"
|
|
69
69
|
},
|
|
70
70
|
"engines": {
|
|
71
71
|
"node": ">=18.0.0"
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"url": "https://github.com/eidestudio/foir-cli.git"
|
|
76
76
|
},
|
|
77
77
|
"scripts": {
|
|
78
|
-
"build": "tsup",
|
|
78
|
+
"build": "tsup && node scripts/smoke-bundle.mjs",
|
|
79
79
|
"dev:cli": "tsx src/cli.ts",
|
|
80
80
|
"check-types": "tsc --noEmit",
|
|
81
81
|
"lint": "eslint src/",
|