@swell/cli 2.6.0 → 2.7.1

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 (46) hide show
  1. package/dist/commands/app/frontend/dev.js +18 -0
  2. package/dist/commands/inspect/content.d.ts +25 -14
  3. package/dist/commands/inspect/content.js +34 -144
  4. package/dist/commands/inspect/extensions.d.ts +49 -0
  5. package/dist/commands/inspect/extensions.js +424 -0
  6. package/dist/commands/inspect/functions.d.ts +48 -0
  7. package/dist/commands/inspect/functions.js +83 -0
  8. package/dist/commands/inspect/index.js +8 -7
  9. package/dist/commands/inspect/models.d.ts +17 -2
  10. package/dist/commands/inspect/models.js +114 -47
  11. package/dist/commands/inspect/notifications.d.ts +31 -0
  12. package/dist/commands/inspect/notifications.js +125 -0
  13. package/dist/commands/inspect/settings.d.ts +28 -0
  14. package/dist/commands/inspect/settings.js +82 -0
  15. package/dist/commands/inspect/webhooks.d.ts +34 -0
  16. package/dist/commands/inspect/webhooks.js +61 -0
  17. package/dist/create-app-command.d.ts +7 -0
  18. package/dist/create-app-command.js +80 -9
  19. package/dist/inspect-resource-command.d.ts +91 -0
  20. package/dist/inspect-resource-command.js +232 -0
  21. package/dist/lib/apps/index.d.ts +2 -1
  22. package/dist/lib/apps/index.js +43 -6
  23. package/dist/lib/apps/inspect-scope.d.ts +58 -0
  24. package/dist/lib/apps/inspect-scope.js +60 -0
  25. package/dist/lib/apps/object-id.d.ts +7 -0
  26. package/dist/lib/apps/object-id.js +9 -0
  27. package/dist/lib/apps/paths.js +8 -1
  28. package/dist/lib/apps/resolve.d.ts +16 -0
  29. package/dist/lib/apps/resolve.js +39 -0
  30. package/dist/lib/apps/slug.d.ts +29 -0
  31. package/dist/lib/apps/slug.js +12 -0
  32. package/dist/lib/inspect/content.d.ts +39 -0
  33. package/dist/lib/inspect/content.js +76 -0
  34. package/dist/lib/inspect/extensions.d.ts +267 -0
  35. package/dist/lib/inspect/extensions.js +690 -0
  36. package/dist/lib/inspect/notifications.d.ts +115 -0
  37. package/dist/lib/inspect/notifications.js +173 -0
  38. package/dist/lib/inspect/settings.d.ts +61 -0
  39. package/dist/lib/inspect/settings.js +56 -0
  40. package/dist/lib/inspect/table.d.ts +29 -0
  41. package/dist/lib/inspect/table.js +61 -0
  42. package/dist/push-app-command.js +3 -2
  43. package/dist/swell-api-command.d.ts +0 -4
  44. package/dist/swell-api-command.js +2 -18
  45. package/oclif.manifest.json +392 -35
  46. package/package.json +1 -1
@@ -0,0 +1,60 @@
1
+ import { HEX24 } from './object-id.js';
2
+ export class ScopeError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = 'ScopeError';
6
+ }
7
+ }
8
+ /**
9
+ * Resolve the scope of an `inspect` subcommand.
10
+ *
11
+ * Grammar:
12
+ * (no --app) → global listing, no filter
13
+ * --app=<slug> → filter by the named app
14
+ * --app=. → filter by the app in the current directory's swell.json
15
+ *
16
+ * The `.` sentinel is the only place swell.json influences behavior; the
17
+ * default is always global so the same invocation produces the same output
18
+ * regardless of cwd.
19
+ */
20
+ export async function resolveInspectScope(ctx, flags = {}) {
21
+ if (!flags.app) {
22
+ return { query: {} };
23
+ }
24
+ if (flags.app === '.') {
25
+ const slug = await ctx.readCurrentAppSlug();
26
+ if (!slug) {
27
+ throw new ScopeError(`--app=. requires a swell.json with an 'id' field in the current directory. Pass --app=<slug> or cd into an app.`);
28
+ }
29
+ const appId = await ctx.resolveAppId(slug);
30
+ return { appId, appSlug: slug, query: { app_id: appId } };
31
+ }
32
+ const appId = await ctx.resolveAppId(flags.app);
33
+ return { appId, appSlug: flags.app, query: { app_id: appId } };
34
+ }
35
+ /**
36
+ * Classify an inspect-subcommand identifier argument.
37
+ *
38
+ * - `id`: 24-char hex (Mongo ObjectId)
39
+ * - `slug`: dotted form `app.<appPart>.<name>` where appPart can be either
40
+ * an ObjectId or a public/private app slug
41
+ * - `name`: bare identifier (letters, digits, hyphen, underscore) —
42
+ * requires app context to resolve
43
+ *
44
+ * Resources whose stored ids are non-hex dotted strings (notifications)
45
+ * handle dispatch in their own `showDetail` override; the base classifier
46
+ * stays narrow.
47
+ */
48
+ export function classifyIdentifier(identifier) {
49
+ if (HEX24.test(identifier)) {
50
+ return { kind: 'id', id: identifier };
51
+ }
52
+ const slugMatch = identifier.match(/^app\.([^.]+)\.(.+)$/);
53
+ if (slugMatch) {
54
+ return { kind: 'slug', appPart: slugMatch[1], name: slugMatch[2] };
55
+ }
56
+ if (/^[\w-]+$/i.test(identifier)) {
57
+ return { kind: 'name', name: identifier };
58
+ }
59
+ return { kind: 'invalid', input: identifier };
60
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Mongo ObjectId shape — 24 hex chars, case-insensitive. The CLI accepts
3
+ * either-case input (curl/jq users tend to lowercase, admin URLs sometimes
4
+ * upper). All inspect grammars use the same pattern, so it lives here.
5
+ */
6
+ export declare const HEX24: RegExp;
7
+ export declare function isObjectId(value: string): boolean;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Mongo ObjectId shape — 24 hex chars, case-insensitive. The CLI accepts
3
+ * either-case input (curl/jq users tend to lowercase, admin URLs sometimes
4
+ * upper). All inspect grammars use the same pattern, so it lives here.
5
+ */
6
+ export const HEX24 = /^[\da-f]{24}$/i;
7
+ export function isObjectId(value) {
8
+ return HEX24.test(value);
9
+ }
@@ -9,13 +9,20 @@ function globOptions(appPath, options) {
9
9
  gitignore: true,
10
10
  ...options,
11
11
  expandDirectories: true,
12
- // Explicitly ignore node_modules and lock files just in case
12
+ // .dev.vars can hold real credentials ignored here in case the
13
+ // user's .gitignore doesn't cover it.
13
14
  ignore: [
14
15
  '**/.swellrc',
15
16
  '**/.git/**',
16
17
  '**/node_modules/**',
17
18
  '**/package-lock.json',
18
19
  '**/yarn.lock',
20
+ '**/pnpm-lock.yaml',
21
+ '**/bun.lockb',
22
+ '**/bun.lock',
23
+ '**/.wrangler/**',
24
+ '**/.dev.vars',
25
+ '**/.dev.vars.*',
19
26
  ...(options?.ignore || []),
20
27
  ],
21
28
  };
@@ -0,0 +1,16 @@
1
+ import Api from '../api.js';
2
+ /**
3
+ * Resolve an app's ObjectId from a public slug or private slug; pass through
4
+ * a value that already looks like an ObjectId.
5
+ *
6
+ * Priority mirrors schema-api-server installer.js:3596 `getAppBySlugId`:
7
+ * private match (`_<slug>`) wins over public match. For typical apps the
8
+ * order is invisible (a single record matches both branches), but it
9
+ * matters when two apps share a slug across publish state.
10
+ */
11
+ export declare function resolveAppId(api: Api, appIdOrSlug: string): Promise<string>;
12
+ /**
13
+ * Build a map of installed-app ObjectId -> friendly slug, using the shared
14
+ * server-canonical slug priority (see `slug.ts`).
15
+ */
16
+ export declare function buildAppSlugMap(api: Api): Promise<Record<string, string>>;
@@ -0,0 +1,39 @@
1
+ import { isObjectId } from './object-id.js';
2
+ import { slugFromInstalledApp } from './slug.js';
3
+ /**
4
+ * Resolve an app's ObjectId from a public slug or private slug; pass through
5
+ * a value that already looks like an ObjectId.
6
+ *
7
+ * Priority mirrors schema-api-server installer.js:3596 `getAppBySlugId`:
8
+ * private match (`_<slug>`) wins over public match. For typical apps the
9
+ * order is invisible (a single record matches both branches), but it
10
+ * matters when two apps share a slug across publish state.
11
+ */
12
+ export async function resolveAppId(api, appIdOrSlug) {
13
+ if (isObjectId(appIdOrSlug)) {
14
+ return appIdOrSlug;
15
+ }
16
+ const installedApps = await api.get({ adminPath: `/client/apps` });
17
+ const records = (installedApps?.results ?? []);
18
+ const privateMatch = records.find((a) => a.app_private_id === `_${appIdOrSlug}`);
19
+ const app = privateMatch ?? records.find((a) => a.app_public_id === appIdOrSlug);
20
+ if (!app) {
21
+ throw new Error(`App '${appIdOrSlug}' not found`);
22
+ }
23
+ return app.app_id;
24
+ }
25
+ /**
26
+ * Build a map of installed-app ObjectId -> friendly slug, using the shared
27
+ * server-canonical slug priority (see `slug.ts`).
28
+ */
29
+ export async function buildAppSlugMap(api) {
30
+ const installedApps = await api.get({ adminPath: `/client/apps` });
31
+ const map = {};
32
+ for (const a of (installedApps?.results ?? [])) {
33
+ const slug = slugFromInstalledApp(a);
34
+ if (a.app_id && slug) {
35
+ map[a.app_id] = slug;
36
+ }
37
+ }
38
+ return map;
39
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Compute an app's display slug.
3
+ *
4
+ * Mirrors the server-canonical priority in
5
+ * schema-api-server/api/admin/features/apps/installer.js:213 — private_id
6
+ * (with the leading underscore stripped) wins over public_id. The orders
7
+ * agree for the typical case where `private_id === '_' + public_id`, but
8
+ * diverge for theme-collision suffixes (`_my_theme_1`) and any app where
9
+ * `public_id` was set independently of `private_id`. Following the server
10
+ * keeps CLI labels in sync with what the admin UI shows for the same row.
11
+ *
12
+ * No id fallback: an app with neither slug returns `undefined`. Callers
13
+ * that want a "show something" guarantee (e.g. the push-app picker) should
14
+ * append `?? app.id` themselves; the slug map deliberately filters such
15
+ * records out so downstream code can flag them as `<not resolved>`.
16
+ */
17
+ export interface SlugSource {
18
+ privateId?: string;
19
+ publicId?: string;
20
+ }
21
+ export declare function appSlug(source: SlugSource): string | undefined;
22
+ export declare function slugFromApp(app: {
23
+ private_id?: string;
24
+ public_id?: string;
25
+ }): string | undefined;
26
+ export declare function slugFromInstalledApp(rec: {
27
+ app_private_id?: string;
28
+ app_public_id?: string;
29
+ }): string | undefined;
@@ -0,0 +1,12 @@
1
+ export function appSlug(source) {
2
+ return source.privateId?.replace(/^_/, '') || source.publicId;
3
+ }
4
+ export function slugFromApp(app) {
5
+ return appSlug({ privateId: app.private_id, publicId: app.public_id });
6
+ }
7
+ export function slugFromInstalledApp(rec) {
8
+ return appSlug({
9
+ privateId: rec.app_private_id,
10
+ publicId: rec.app_public_id,
11
+ });
12
+ }
@@ -0,0 +1,39 @@
1
+ import { GroupInfo } from './table.js';
2
+ export interface ParsedContentId {
3
+ middle: string;
4
+ name: string;
5
+ prefix: 'app' | 'custom';
6
+ }
7
+ /**
8
+ * Parse a content view identifier of the form `app.<appPart>.<name>` or
9
+ * `custom.<source>.<name>`. Returns null for anything that doesn't match.
10
+ */
11
+ export declare function parseContentId(id: string): ParsedContentId | null;
12
+ /**
13
+ * Produce the column-1 paste-back key for a content view. Rewrites
14
+ * `app.<hex>.<name>` to `app.<slug>.<name>` when the slug is known; passes
15
+ * `custom.*` and unresolvable app ids through unchanged (both remain valid
16
+ * paste-back forms against the detail endpoint).
17
+ */
18
+ export declare function resolveContentKey(id: string, appSlugById: Record<string, string>): string;
19
+ /**
20
+ * Group a content view record for the vertical split. Records with no
21
+ * `app_id` belong to the platform's `custom.*` source and land in a
22
+ * single `<custom>` section at the top of the output.
23
+ */
24
+ export declare function groupContentRecord(record: {
25
+ app_id?: string;
26
+ }, appSlugById: Record<string, string>): GroupInfo;
27
+ /**
28
+ * Translate a user-supplied content identifier into the unresolved API
29
+ * form. Accepts:
30
+ * - `custom.<source>.<name>` → unchanged
31
+ * - `app.<hex>.<name>` → unchanged (already unresolved form)
32
+ * - `app.<slug>.<name>` → slug resolved to hex
33
+ * - bare `<name>` → combined with scope.appId to `app.<hex>.<name>`
34
+ *
35
+ * `resolveAppId` is injected so this function is testable without a live API.
36
+ */
37
+ export declare function reverseResolveContentId(identifier: string, scope: {
38
+ appId?: string;
39
+ }, resolveAppId: (slug: string) => Promise<string>): Promise<string>;
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Parse a content view identifier of the form `app.<appPart>.<name>` or
3
+ * `custom.<source>.<name>`. Returns null for anything that doesn't match.
4
+ */
5
+ export function parseContentId(id) {
6
+ const match = id.match(/^(app|custom)\.([^.]+)\.(.+)$/);
7
+ if (!match)
8
+ return null;
9
+ return {
10
+ prefix: match[1],
11
+ middle: match[2],
12
+ name: match[3],
13
+ };
14
+ }
15
+ /**
16
+ * Produce the column-1 paste-back key for a content view. Rewrites
17
+ * `app.<hex>.<name>` to `app.<slug>.<name>` when the slug is known; passes
18
+ * `custom.*` and unresolvable app ids through unchanged (both remain valid
19
+ * paste-back forms against the detail endpoint).
20
+ */
21
+ export function resolveContentKey(id, appSlugById) {
22
+ const parsed = parseContentId(id);
23
+ if (!parsed)
24
+ return id;
25
+ if (parsed.prefix === 'custom')
26
+ return id;
27
+ const slug = appSlugById[parsed.middle] ?? parsed.middle;
28
+ return `app.${slug}.${parsed.name}`;
29
+ }
30
+ /**
31
+ * Group a content view record for the vertical split. Records with no
32
+ * `app_id` belong to the platform's `custom.*` source and land in a
33
+ * single `<custom>` section at the top of the output.
34
+ */
35
+ export function groupContentRecord(record, appSlugById) {
36
+ if (!record.app_id) {
37
+ return { slug: '<custom>', label: 'custom', order: 0 };
38
+ }
39
+ const resolved = appSlugById[record.app_id];
40
+ if (!resolved) {
41
+ return { slug: '<not resolved>', order: 2 };
42
+ }
43
+ return { slug: resolved };
44
+ }
45
+ /**
46
+ * Translate a user-supplied content identifier into the unresolved API
47
+ * form. Accepts:
48
+ * - `custom.<source>.<name>` → unchanged
49
+ * - `app.<hex>.<name>` → unchanged (already unresolved form)
50
+ * - `app.<slug>.<name>` → slug resolved to hex
51
+ * - bare `<name>` → combined with scope.appId to `app.<hex>.<name>`
52
+ *
53
+ * `resolveAppId` is injected so this function is testable without a live API.
54
+ */
55
+ export async function reverseResolveContentId(identifier, scope, resolveAppId) {
56
+ if (/^custom\.[^.]+\..+$/.test(identifier)) {
57
+ return identifier;
58
+ }
59
+ if (/^app\.[\da-f]{24}\..+$/i.test(identifier)) {
60
+ return identifier;
61
+ }
62
+ const appMatch = identifier.match(/^app\.([^.]+)\.(.+)$/);
63
+ if (appMatch) {
64
+ const appId = await resolveAppId(appMatch[1]);
65
+ return `app.${appId}.${appMatch[2]}`;
66
+ }
67
+ if (/^[\w-]+$/i.test(identifier)) {
68
+ if (!scope.appId) {
69
+ throw new Error(`Bare content name '${identifier}' requires --app=<slug> or --app=. to scope. ` +
70
+ `Alternatively, pass a full identifier (app.<app>.<name> or custom.<source>.<name>).`);
71
+ }
72
+ return `app.${scope.appId}.${identifier}`;
73
+ }
74
+ throw new Error(`Invalid content identifier '${identifier}'. ` +
75
+ `Expected bare name, app.<app>.<name>, or custom.<source>.<name>.`);
76
+ }
@@ -0,0 +1,267 @@
1
+ import { GroupInfo } from './table.js';
2
+ /**
3
+ * Manifest entry shape, matching `extensions[]` in the deployed app record
4
+ * and in local `./swell.json`. Mirrors the schema at
5
+ * schema-api-server/api/admin/models/apps.json:429-491.
6
+ */
7
+ export interface ManifestEntry {
8
+ id: string;
9
+ type: 'payment' | 'tax' | 'shipping';
10
+ name?: string;
11
+ description?: string;
12
+ setting?: string;
13
+ subscriptions?: boolean;
14
+ method?: string;
15
+ gateway?: string;
16
+ method_logo_src?: string;
17
+ method_icon_src?: string;
18
+ gateway_logo_src?: string;
19
+ gateway_icon_src?: string;
20
+ carrier?: string;
21
+ carrier_logo_src?: string;
22
+ carrier_icon_src?: string;
23
+ [key: string]: unknown;
24
+ }
25
+ /** CLI-resolved type. Diverges from raw manifest `type` for payment (card vs alt). */
26
+ export type ExtensionType = 'card' | 'alt' | 'shipping' | 'tax';
27
+ export type ExtensionStatus = 'not deployed' | 'not activated' | 'app id mismatch' | 'id mismatch' | 'not selected' | 'gateway missing' | 'not enabled' | 'no handler' | 'handler mismatch' | 'activated';
28
+ export type ActionOwner = 'dev' | 'merchant' | null;
29
+ export interface FunctionRecord {
30
+ id?: string;
31
+ name?: string;
32
+ enabled?: boolean;
33
+ app_id?: string;
34
+ extension?: string;
35
+ model?: {
36
+ events?: string[];
37
+ };
38
+ }
39
+ export interface ComponentRecord {
40
+ id?: string;
41
+ name?: string;
42
+ file_path?: string;
43
+ values?: {
44
+ [key: string]: unknown;
45
+ extension?: string;
46
+ };
47
+ [key: string]: unknown;
48
+ }
49
+ export interface PaymentMethodRecord {
50
+ id?: string;
51
+ name?: string;
52
+ gateway?: string;
53
+ extension_app_id?: string;
54
+ extension_config_id?: string;
55
+ enabled?: boolean;
56
+ activated?: boolean;
57
+ [key: string]: unknown;
58
+ }
59
+ export interface PaymentGatewayRecord {
60
+ id?: string;
61
+ name?: string;
62
+ extension_app_id?: string;
63
+ extension_config_id?: string;
64
+ [key: string]: unknown;
65
+ }
66
+ export interface ShippingCarrierRecord {
67
+ id?: string;
68
+ name?: string;
69
+ enabled?: boolean;
70
+ extension_app_id?: string;
71
+ extension_config_id?: string;
72
+ [key: string]: unknown;
73
+ }
74
+ export interface TaxSettingsRecord {
75
+ extension_app_id?: string;
76
+ extension_config_id?: string;
77
+ [key: string]: unknown;
78
+ }
79
+ export interface PaymentSettings {
80
+ methods?: PaymentMethodRecord[];
81
+ gateways?: PaymentGatewayRecord[];
82
+ [key: string]: unknown;
83
+ }
84
+ export interface ShippingSettings {
85
+ carriers?: ShippingCarrierRecord[];
86
+ [key: string]: unknown;
87
+ }
88
+ export interface NativeBinding {
89
+ path: string;
90
+ record: unknown;
91
+ field_checks: Record<string, boolean>;
92
+ }
93
+ export interface BoundFunction {
94
+ id?: string;
95
+ name?: string;
96
+ extension?: string;
97
+ enabled?: boolean;
98
+ model?: {
99
+ events?: string[];
100
+ };
101
+ }
102
+ export interface BoundComponent {
103
+ id?: string;
104
+ name?: string;
105
+ extension?: string;
106
+ file_path?: string;
107
+ }
108
+ export interface LocalDiff {
109
+ changed_fields: string[];
110
+ local: ManifestEntry;
111
+ deployed: ManifestEntry;
112
+ }
113
+ export interface ExtensionDetail {
114
+ status: ExtensionStatus;
115
+ action_owner: ActionOwner;
116
+ action: string | null;
117
+ type: ExtensionType | null;
118
+ manifest: ManifestEntry | null;
119
+ native_bindings: NativeBinding[];
120
+ bound: {
121
+ components: BoundComponent[];
122
+ functions: BoundFunction[];
123
+ };
124
+ required_events: string[];
125
+ missing_required_events: string[];
126
+ local_diff: LocalDiff | null;
127
+ }
128
+ /** Parsed identifier for `swell inspect extensions <id>`. */
129
+ export type ParsedExtensionKey = {
130
+ appPart: string;
131
+ extId: string;
132
+ kind: 'slug';
133
+ } | {
134
+ kind: 'name';
135
+ name: string;
136
+ } | {
137
+ input: string;
138
+ kind: 'invalid';
139
+ };
140
+ /**
141
+ * Parse a `swell inspect extensions` identifier.
142
+ *
143
+ * Accepts:
144
+ * - `app.<slug>.<extId>` → kind 'slug'
145
+ * - bare `<extId>` → kind 'name' (requires --app= scope at the call site)
146
+ *
147
+ * 24-char hex is intentionally NOT a valid form — the synthesized resource has
148
+ * no canonical 24-char id. Callers should reject hex shapes upstream so the
149
+ * error message can be clear about the cause.
150
+ */
151
+ export declare function parseExtensionKey(input: string): ParsedExtensionKey;
152
+ /** Build the column-1 paste-back key for a row. */
153
+ export declare function formatExtensionKey(slug: string, extId: string): string;
154
+ /**
155
+ * Resolve a manifest entry to the CLI's type taxonomy. Payment splits on
156
+ * `method`: when `method === 'card'` the extension provides a card gateway;
157
+ * otherwise it's an alt method. The platform formula at
158
+ * schema-api-server/api/admin/models/apps.json:457-458 defaults `method` to
159
+ * `id` when unset, so a payment extension whose `id` happens to be `card`
160
+ * resolves to `card` even with no explicit `method`.
161
+ */
162
+ export declare function resolveExtensionType(entry: Pick<ManifestEntry, 'type' | 'method' | 'id'>): ExtensionType;
163
+ /** Apply the platform's `if(method, method, id)` formula. */
164
+ export declare function paymentMethodIdFor(entry: Pick<ManifestEntry, 'method' | 'id'>): string;
165
+ /** Shipping records use `app_<appId>_<extId>` as both the row id and the carrier id. */
166
+ export declare function shippingBindId(appId: string, extId: string): string;
167
+ /** Card-gateway records use the same id form as shipping rows. */
168
+ export declare function gatewayBindId(appId: string, extId: string): string;
169
+ /**
170
+ * Required events per extension type, in the `<model>/<event>` form that
171
+ * function records actually store.
172
+ *
173
+ * Verified against:
174
+ * schema-api-server/server/vault.js:1818,1894 (payment.create_intent)
175
+ * schema-api-server/api/com/features/payments/index.js:701-757
176
+ * (payment.charge, payment.refund, dispatcher gate)
177
+ * schema-api-server/server/vault.js:2304-2322 (card-gateway intent path)
178
+ * schema-api-server/api/com/features/orders/shipping.js:411-417,424-431,452-458
179
+ * (shipping dispatch + enabled gate)
180
+ * schema-api-server/api/com/features/orders/taxes.js:123-176
181
+ * (tax dispatch)
182
+ * schema-api-server/api/com/features/orders/extensions.test.js:85,95,179,271,290,303,671,704
183
+ * (event-name format fixtures)
184
+ */
185
+ export declare function requiredEventsFor(type: ExtensionType): string[];
186
+ /**
187
+ * Strip `before:` / `after:` hook-type prefix from an event identifier.
188
+ *
189
+ * Both prefixes are platform-supported on function records; bare events
190
+ * default to `after`. For required-event coverage we treat any prefix as
191
+ * equivalent so that a function declaring `before:payment.charge` covers a
192
+ * required `payments/payment.charge`. Cite:
193
+ * schema-api-server/api/com/features/orders/extensions.test.js:95.
194
+ */
195
+ export declare function stripHookPrefix(event: string): string;
196
+ /**
197
+ * Compute the set of bare event identifiers covered by a function record's
198
+ * `model.events` list. The function event format is `<model>/<event>` with
199
+ * an optional `before:`/`after:` prefix on the bare event part. Returns the
200
+ * full `<model>/<event>` string with hook prefixes stripped from the event.
201
+ */
202
+ export declare function eventsCoveredByFunctions(functions: Pick<FunctionRecord, 'model'>[]): Set<string>;
203
+ /** Required events not covered by any of the bound functions. */
204
+ export declare function missingRequiredEvents(type: ExtensionType, functions: Pick<FunctionRecord, 'model'>[]): string[];
205
+ /** Lift a component record into the synthesized `BoundComponent` shape. */
206
+ export declare function liftBoundComponent(record: ComponentRecord): BoundComponent;
207
+ /** Lift a function record into the synthesized `BoundFunction` shape. */
208
+ export declare function liftBoundFunction(record: FunctionRecord): BoundFunction;
209
+ /**
210
+ * Compare a local manifest entry against the deployed entry. Uses deep
211
+ * equality for change detection and reports differing top-level keys. When
212
+ * the entries are equal, returns `null`.
213
+ */
214
+ export declare function diffManifestEntries(local: ManifestEntry, deployed: ManifestEntry): LocalDiff | null;
215
+ export interface BuildDetailInput {
216
+ appId: string;
217
+ appSlug: string;
218
+ extId: string;
219
+ manifest: ManifestEntry | null;
220
+ /** True when the local manifest has the entry but the deployed app record doesn't. */
221
+ notDeployed?: boolean;
222
+ payments?: PaymentSettings | null;
223
+ shipments?: ShippingSettings | null;
224
+ taxes?: TaxSettingsRecord | null;
225
+ /** Functions in the same app whose top-level `extension` matches this extId. */
226
+ functions: FunctionRecord[];
227
+ /** Components in the same app whose `values.extension` matches this extId. */
228
+ components: ComponentRecord[];
229
+ localDiff?: LocalDiff | null;
230
+ }
231
+ export interface BuildOrphanInput {
232
+ appId: string;
233
+ appSlug: string;
234
+ /** The unresolved extension value taken from the function/component. */
235
+ unresolvedId: string;
236
+ functions: FunctionRecord[];
237
+ components: ComponentRecord[];
238
+ }
239
+ /** Build the synthesized envelope for a regular (non-orphan) extension row. */
240
+ export declare function buildExtensionDetail(input: BuildDetailInput): ExtensionDetail;
241
+ /** Build the synthesized envelope for an orphan row. */
242
+ export declare function buildOrphanDetail(input: BuildOrphanInput): ExtensionDetail;
243
+ /**
244
+ * List-mode meta string for one row. Returns `undefined` when the row has
245
+ * nothing surface-worthy beyond the type tag (which is always present).
246
+ */
247
+ export declare function listMetaFor(detail: ExtensionDetail): string;
248
+ /**
249
+ * List-mode meta string for an orphan row. The type tag is replaced with
250
+ * the status word since there's no manifest to derive type from.
251
+ */
252
+ export declare function orphanListMeta(fnCount: number, compCount: number): string;
253
+ /**
254
+ * `Next steps:` lines per status. Runnable shell commands come first,
255
+ * merchant-UI lines come second prefixed with `(merchant)` so an agent can
256
+ * filter. Empty array when there are no actionable next steps.
257
+ */
258
+ export declare function nextStepLines(detail: ExtensionDetail): string[];
259
+ /**
260
+ * Section ordering for the list view. Apps render alphabetically; orphan
261
+ * rows land in a single trailing `<orphans>` group.
262
+ */
263
+ export declare function listGroupForApp(slug: string): GroupInfo;
264
+ export declare function listGroupForOrphans(): GroupInfo;
265
+ /** True when a function/component "belongs to" an extension by direct match. */
266
+ export declare function functionMatchesExtension(fn: FunctionRecord, extId: string): boolean;
267
+ export declare function componentMatchesExtension(comp: ComponentRecord, extId: string): boolean;