@swell/cli 2.6.0 → 2.7.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.
Files changed (42) 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/functions.d.ts +48 -0
  5. package/dist/commands/inspect/functions.js +83 -0
  6. package/dist/commands/inspect/index.js +8 -7
  7. package/dist/commands/inspect/models.d.ts +17 -2
  8. package/dist/commands/inspect/models.js +114 -47
  9. package/dist/commands/inspect/notifications.d.ts +31 -0
  10. package/dist/commands/inspect/notifications.js +125 -0
  11. package/dist/commands/inspect/settings.d.ts +28 -0
  12. package/dist/commands/inspect/settings.js +82 -0
  13. package/dist/commands/inspect/webhooks.d.ts +34 -0
  14. package/dist/commands/inspect/webhooks.js +61 -0
  15. package/dist/create-app-command.d.ts +7 -0
  16. package/dist/create-app-command.js +66 -3
  17. package/dist/inspect-resource-command.d.ts +91 -0
  18. package/dist/inspect-resource-command.js +232 -0
  19. package/dist/lib/apps/index.d.ts +2 -1
  20. package/dist/lib/apps/index.js +43 -6
  21. package/dist/lib/apps/inspect-scope.d.ts +58 -0
  22. package/dist/lib/apps/inspect-scope.js +60 -0
  23. package/dist/lib/apps/object-id.d.ts +7 -0
  24. package/dist/lib/apps/object-id.js +9 -0
  25. package/dist/lib/apps/paths.js +8 -1
  26. package/dist/lib/apps/resolve.d.ts +16 -0
  27. package/dist/lib/apps/resolve.js +39 -0
  28. package/dist/lib/apps/slug.d.ts +29 -0
  29. package/dist/lib/apps/slug.js +12 -0
  30. package/dist/lib/inspect/content.d.ts +39 -0
  31. package/dist/lib/inspect/content.js +76 -0
  32. package/dist/lib/inspect/notifications.d.ts +115 -0
  33. package/dist/lib/inspect/notifications.js +173 -0
  34. package/dist/lib/inspect/settings.d.ts +61 -0
  35. package/dist/lib/inspect/settings.js +56 -0
  36. package/dist/lib/inspect/table.d.ts +29 -0
  37. package/dist/lib/inspect/table.js +61 -0
  38. package/dist/push-app-command.js +3 -2
  39. package/dist/swell-api-command.d.ts +0 -4
  40. package/dist/swell-api-command.js +2 -18
  41. package/oclif.manifest.json +328 -35
  42. package/package.json +1 -1
@@ -0,0 +1,56 @@
1
+ const PATH_SEGMENT = /^[\w.-]+$/;
2
+ const APP_PREFIX = /^app\.([^.]+)$/;
3
+ export function parseSettingsKey(input) {
4
+ const appMatch = input.match(APP_PREFIX);
5
+ if (appMatch) {
6
+ return { kind: 'app', ref: appMatch[1] };
7
+ }
8
+ if (input.startsWith('app.')) {
9
+ return { kind: 'invalid', input };
10
+ }
11
+ if (PATH_SEGMENT.test(input)) {
12
+ return { kind: 'path', segment: input };
13
+ }
14
+ return { kind: 'invalid', input };
15
+ }
16
+ /**
17
+ * Produce the column-1 paste-back key for a settings record.
18
+ *
19
+ * System (no app_id): `record.name` (e.g. `taxes`).
20
+ * App with slug: `app.<slug>`.
21
+ * App without slug: `app.<hex>` (still pasteable — `app.<hex>` resolves).
22
+ *
23
+ * App records carry `name === app_id` (the hex), which is meaningless to a
24
+ * human; we never surface it. Ultimate fallback is `record.id` for degenerate
25
+ * records that have neither `app_id` nor `name`.
26
+ */
27
+ export function formatSettingsKey(record, appSlugById) {
28
+ if (!record.app_id) {
29
+ return record.name ?? record.id ?? '-';
30
+ }
31
+ const slug = appSlugById[record.app_id] ?? record.app_id;
32
+ return `app.${slug}`;
33
+ }
34
+ /**
35
+ * Group a settings record. System rows (`app_id` null/undefined) belong to the
36
+ * platform and render under a `system` divider at the top, mirroring the
37
+ * notifications shape.
38
+ */
39
+ export function groupSettingsRecord(record, appSlugById) {
40
+ if (!record.app_id) {
41
+ return { slug: '<system>', label: 'system', order: 0 };
42
+ }
43
+ const resolved = appSlugById[record.app_id];
44
+ if (!resolved) {
45
+ return { slug: '<not resolved>', order: 2 };
46
+ }
47
+ return { slug: resolved };
48
+ }
49
+ /**
50
+ * `general` and `admin` are flagged `deprecated: true` in the base schema and
51
+ * still come back from the default list (no server-side filter equivalent —
52
+ * `deprecated[$ne]=true` doesn't take). Filter them out client-side.
53
+ */
54
+ export function isDeprecatedRecord(record) {
55
+ return record.deprecated === true;
56
+ }
@@ -0,0 +1,29 @@
1
+ export interface GroupInfo {
2
+ /** Grouping identity. Rows with the same slug land in the same section. */
3
+ slug: string;
4
+ /** Divider text. Defaults to `slug` when omitted. */
5
+ label?: string;
6
+ /** Sort priority; lower renders first. Ties preserve insertion order. Default 1. */
7
+ order?: number;
8
+ }
9
+ export interface KeyMetaRow {
10
+ /** Paste-back identifier rendered as column 1. */
11
+ key: string;
12
+ /** Optional compact status string. Empty or undefined renders the key alone. */
13
+ meta?: string;
14
+ /** Optional group; when some rows carry one, multiple groups produce dividers. */
15
+ group?: GroupInfo;
16
+ }
17
+ /**
18
+ * Render a two-column list: paste-back key + optional compact meta string.
19
+ *
20
+ * - Rows with meta render as ` <key-padded> <meta>`.
21
+ * - Rows without meta render as ` <key>` with no trailing whitespace.
22
+ * - Key column width is computed globally across all rows so meta columns
23
+ * align vertically.
24
+ * - Rows with a `group` are collected into sections; sections are separated
25
+ * by a blank line and a `── <label>` divider. Sections sort by `order`
26
+ * (lower first), ties preserve insertion order. A single group collapses
27
+ * to a flat listing.
28
+ */
29
+ export declare function renderKeyMetaTable(rows: KeyMetaRow[]): string[];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Render a two-column list: paste-back key + optional compact meta string.
3
+ *
4
+ * - Rows with meta render as ` <key-padded> <meta>`.
5
+ * - Rows without meta render as ` <key>` with no trailing whitespace.
6
+ * - Key column width is computed globally across all rows so meta columns
7
+ * align vertically.
8
+ * - Rows with a `group` are collected into sections; sections are separated
9
+ * by a blank line and a `── <label>` divider. Sections sort by `order`
10
+ * (lower first), ties preserve insertion order. A single group collapses
11
+ * to a flat listing.
12
+ */
13
+ export function renderKeyMetaTable(rows) {
14
+ if (rows.length === 0) {
15
+ return [];
16
+ }
17
+ const keyWidth = Math.max(...rows.map((r) => r.key.length));
18
+ const formatRow = (row) => {
19
+ if (row.meta && row.meta.length > 0) {
20
+ return ` ${row.key.padEnd(keyWidth)} ${row.meta}`;
21
+ }
22
+ return ` ${row.key}`;
23
+ };
24
+ const hasGroups = rows.some((r) => r.group !== undefined);
25
+ if (!hasGroups) {
26
+ return rows.map((row) => formatRow(row));
27
+ }
28
+ const groupMap = new Map();
29
+ for (const row of rows) {
30
+ const info = row.group;
31
+ if (!info)
32
+ continue;
33
+ const existing = groupMap.get(info.slug);
34
+ if (existing) {
35
+ existing.rows.push(row);
36
+ }
37
+ else {
38
+ groupMap.set(info.slug, {
39
+ slug: info.slug,
40
+ label: info.label ?? info.slug,
41
+ order: info.order ?? 1,
42
+ rows: [row],
43
+ });
44
+ }
45
+ }
46
+ if (groupMap.size === 1) {
47
+ return rows.map((row) => formatRow(row));
48
+ }
49
+ const ordered = [...groupMap.values()].sort((a, b) => a.order - b.order);
50
+ const lines = [];
51
+ for (const group of ordered) {
52
+ if (lines.length > 0) {
53
+ lines.push('');
54
+ }
55
+ lines.push(`── ${group.label}`);
56
+ for (const row of group.rows) {
57
+ lines.push(formatRow(row));
58
+ }
59
+ }
60
+ return lines;
61
+ }
@@ -6,8 +6,9 @@ import * as path from 'node:path';
6
6
  import Stream from 'node:stream';
7
7
  import ora from 'ora';
8
8
  import { default as swellConfig } from './lib/app-config.js';
9
- import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, CUSTOM_FRAMEWORK_SLUG, appConfigFromFile, filePathExists, filePathExistsAsync, findAppConfig, getAppSlugId, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, getProjectCommands, globAllFilesByPath, hashString, isPathDirectory, } from './lib/apps/index.js';
9
+ import { ConfigType, getFrontendProjectValidValues, allConfigFilesInDir, CUSTOM_FRAMEWORK_SLUG, appConfigFromFile, filePathExists, filePathExistsAsync, findAppConfig, getConfigTypeFromPath, getConfigTypeKeyFromValue, getFrontendProjectType, getProjectCommands, globAllFilesByPath, hashString, isPathDirectory, } from './lib/apps/index.js';
10
10
  import { getGlobIgnorePathsChecker } from './lib/apps/paths.js';
11
+ import { slugFromApp } from './lib/apps/slug.js';
11
12
  import { default as localConfig } from './lib/config.js';
12
13
  import { toAppId } from './lib/create/index.js';
13
14
  import { detectPackageManager, transformCommand, } from './lib/package-manager.js';
@@ -139,7 +140,7 @@ export class PushAppCommand extends RemoteAppCommand {
139
140
  }
140
141
  const appSlugId = (await select({
141
142
  choices: apps.results.map((app) => ({
142
- name: `${style.appConfigValue(app.name)} (${getAppSlugId(app)})`,
143
+ name: `${style.appConfigValue(app.name)} (${slugFromApp(app) ?? app.id})`,
143
144
  value: app.private_id,
144
145
  })),
145
146
  message: `Choose ${typeLabelPrefixed} to pull`,
@@ -6,10 +6,6 @@ export declare abstract class SwellApiCommand extends SwellCommand {
6
6
  protected request(command: typeof SwellApiCommand, requestOptions?: Api.RequestOptions): Promise<void>;
7
7
  protected catch(error: Error): Promise<any>;
8
8
  private parseCommand;
9
- /**
10
- * Resolve app ObjectId from a friendly slug or return as-is if already an ObjectId.
11
- */
12
- private resolveAppId;
13
9
  /**
14
10
  * Resolve function ID from app ID and function name.
15
11
  */
@@ -3,6 +3,7 @@ import * as fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { FetchError } from 'node-fetch';
5
5
  import { HttpMethod } from './lib/api.js';
6
+ import { resolveAppId } from './lib/apps/resolve.js';
6
7
  import { SwellCommand } from './swell-command.js';
7
8
  // Pattern to match /functions/{appId}/{functionName} with optional query string
8
9
  const FUNCTION_PATH_REGEX = /^\/functions\/([^/]+)\/([^/?]+)(\?.*)?$/;
@@ -85,28 +86,11 @@ export class SwellApiCommand extends SwellCommand {
85
86
  },
86
87
  };
87
88
  }
88
- /**
89
- * Resolve app ObjectId from a friendly slug or return as-is if already an ObjectId.
90
- */
91
- async resolveAppId(appIdOrSlug) {
92
- // If it looks like an ObjectId (24 hex chars), return as-is
93
- if (/^[\da-f]{24}$/i.test(appIdOrSlug)) {
94
- return appIdOrSlug;
95
- }
96
- // Fetch all installed apps and filter by public_id or private_id client-side
97
- const installedApps = await this.api.get({ adminPath: `/client/apps` });
98
- const app = installedApps?.results?.find((a) => a.app_public_id === appIdOrSlug ||
99
- a.app_private_id === `_${appIdOrSlug}`);
100
- if (!app) {
101
- throw new Error(`App '${appIdOrSlug}' not found`);
102
- }
103
- return app.app_id;
104
- }
105
89
  /**
106
90
  * Resolve function ID from app ID and function name.
107
91
  */
108
92
  async resolveFunctionId(appIdOrSlug, functionName) {
109
- const appId = await this.resolveAppId(appIdOrSlug);
93
+ const appId = await resolveAppId(this.api, appIdOrSlug);
110
94
  const functionRecord = await this.api.get({ adminPath: `/data/:functions` }, {
111
95
  query: {
112
96
  app_id: appId,