@faable/faable 4.8.0 → 4.9.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.
- package/dist/commands/auth/clients/get.js +3 -0
- package/dist/commands/auth/clients/index.js +2 -0
- package/dist/commands/auth/clients/profile_fields.js +121 -0
- package/dist/commands/auth/clients/update.js +146 -0
- package/dist/commands/upgrade/index.js +17 -7
- package/dist/lib/self_upgrade.js +51 -0
- package/dist/mcp/tools.js +47 -0
- package/package.json +2 -2
|
@@ -2,6 +2,7 @@ import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
|
|
|
2
2
|
import { log } from '../../../log.js';
|
|
3
3
|
import { tenant_options } from '../options.js';
|
|
4
4
|
import { json_option, print_json } from '../../../lib/listing.js';
|
|
5
|
+
import { format_field } from './profile_fields.js';
|
|
5
6
|
import { resolve_client } from './resolve.js';
|
|
6
7
|
|
|
7
8
|
const clients_get = {
|
|
@@ -39,6 +40,8 @@ const clients_get = {
|
|
|
39
40
|
if (client.web_origins?.length) {
|
|
40
41
|
log.info(` Web origins: ${client.web_origins.join(', ')}`);
|
|
41
42
|
}
|
|
43
|
+
const fields = client.profile_fields;
|
|
44
|
+
log.info(` Fields: ${fields ? (fields.length ? fields.map(format_field).join(', ') : 'none') : 'inherited from the tenant'}`);
|
|
42
45
|
log.info(` Created: ${client.createdAt ?? '-'}`);
|
|
43
46
|
})
|
|
44
47
|
};
|
|
@@ -2,6 +2,7 @@ import { clients_create } from './create.js';
|
|
|
2
2
|
import { clients_get } from './get.js';
|
|
3
3
|
import { clients_list } from './list.js';
|
|
4
4
|
import { clients_rm } from './rm.js';
|
|
5
|
+
import { clients_update } from './update.js';
|
|
5
6
|
|
|
6
7
|
const clients = {
|
|
7
8
|
command: 'clients',
|
|
@@ -10,6 +11,7 @@ const clients = {
|
|
|
10
11
|
.command(clients_list)
|
|
11
12
|
.command(clients_get)
|
|
12
13
|
.command(clients_create)
|
|
14
|
+
.command(clients_update)
|
|
13
15
|
.command(clients_rm)
|
|
14
16
|
.demandCommand(1)
|
|
15
17
|
.showHelpOnFail(false),
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
// The `profile_fields` of a client: which custom fields its users may write on
|
|
2
|
+
// themselves with `PATCH /me` (`auth-js updateProfile`), stored in
|
|
3
|
+
// `user_metadata["faable.com/fields"]`. A client's list REPLACES the tenant's
|
|
4
|
+
// for that app; no list (`null`) inherits the tenant's.
|
|
5
|
+
const KEY = /^[a-z0-9_]{1,40}$/;
|
|
6
|
+
const invalid = (spec, why) => new Error(`Invalid field "${spec}": ${why}`);
|
|
7
|
+
const checkKey = (key, spec) => {
|
|
8
|
+
if (!KEY.test(key)) {
|
|
9
|
+
throw invalid(spec, 'the key takes lowercase letters, digits and _ (1-40 characters)');
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* `--add-field` syntax:
|
|
14
|
+
*
|
|
15
|
+
* profile a string field, default max length (200)
|
|
16
|
+
* school:string:120 a string field of at most 120 characters
|
|
17
|
+
* profile:enum:student,teacher one of the listed values
|
|
18
|
+
*/
|
|
19
|
+
const parse_field_spec = (spec) => {
|
|
20
|
+
const [key, type, rest] = spec.includes(':')
|
|
21
|
+
? splitSpec(spec)
|
|
22
|
+
: [spec, 'string', undefined];
|
|
23
|
+
checkKey(key, spec);
|
|
24
|
+
if (type === 'enum') {
|
|
25
|
+
const options = (rest ?? '')
|
|
26
|
+
.split(',')
|
|
27
|
+
.map(o => o.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
if (!options.length) {
|
|
30
|
+
throw invalid(spec, 'an enum needs its values: key:enum:a,b,c');
|
|
31
|
+
}
|
|
32
|
+
return { key, type: 'enum', options: [...new Set(options)] };
|
|
33
|
+
}
|
|
34
|
+
if (type === 'string') {
|
|
35
|
+
if (rest === undefined || rest === '')
|
|
36
|
+
return { key, type: 'string' };
|
|
37
|
+
const max = Number(rest);
|
|
38
|
+
if (!Number.isInteger(max) || max < 1 || max > 1000) {
|
|
39
|
+
throw invalid(spec, 'the max length is a whole number from 1 to 1000');
|
|
40
|
+
}
|
|
41
|
+
return { key, type: 'string', max_length: max };
|
|
42
|
+
}
|
|
43
|
+
throw invalid(spec, `unknown type "${type}" (string or enum)`);
|
|
44
|
+
};
|
|
45
|
+
// `key:type:rest`, where `rest` may itself contain `:` (an enum value).
|
|
46
|
+
const splitSpec = (spec) => {
|
|
47
|
+
const first = spec.indexOf(':');
|
|
48
|
+
const key = spec.slice(0, first);
|
|
49
|
+
const after = spec.slice(first + 1);
|
|
50
|
+
const second = after.indexOf(':');
|
|
51
|
+
if (second < 0)
|
|
52
|
+
return [key, after, undefined];
|
|
53
|
+
return [key, after.slice(0, second), after.slice(second + 1)];
|
|
54
|
+
};
|
|
55
|
+
/** `--fields-json`: the whole list, validated the same way. */
|
|
56
|
+
const parse_fields_json = (raw) => {
|
|
57
|
+
let parsed;
|
|
58
|
+
try {
|
|
59
|
+
parsed = JSON.parse(raw);
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new Error('--fields-json is not valid JSON');
|
|
63
|
+
}
|
|
64
|
+
if (!Array.isArray(parsed)) {
|
|
65
|
+
throw new Error('--fields-json must be a JSON array of field declarations');
|
|
66
|
+
}
|
|
67
|
+
return parsed.map((f, i) => {
|
|
68
|
+
const d = f;
|
|
69
|
+
const spec = `--fields-json[${i}]`;
|
|
70
|
+
if (typeof d?.key !== 'string')
|
|
71
|
+
throw invalid(spec, 'missing "key"');
|
|
72
|
+
checkKey(d.key, spec);
|
|
73
|
+
if (d.type === 'enum') {
|
|
74
|
+
if (!Array.isArray(d.options) || !d.options.length) {
|
|
75
|
+
throw invalid(spec, 'an enum needs "options"');
|
|
76
|
+
}
|
|
77
|
+
return { key: d.key, type: 'enum', options: d.options.map(String) };
|
|
78
|
+
}
|
|
79
|
+
if (d.type !== 'string') {
|
|
80
|
+
throw invalid(spec, '"type" is "string" or "enum"');
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
key: d.key,
|
|
84
|
+
type: 'string',
|
|
85
|
+
...(d.max_length !== undefined ? { max_length: d.max_length } : {})
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* The client's new list after removing and adding. Adding a key that is
|
|
91
|
+
* already declared replaces its declaration in place; removing a key that is
|
|
92
|
+
* not there is an error, so a typo does not look like success.
|
|
93
|
+
*/
|
|
94
|
+
const apply_field_changes = (current, adds, removes) => {
|
|
95
|
+
const next = [...(current ?? [])];
|
|
96
|
+
for (const key of removes) {
|
|
97
|
+
const at = next.findIndex(f => f.key === key);
|
|
98
|
+
if (at < 0) {
|
|
99
|
+
const have = next.map(f => f.key).join(', ') || 'none';
|
|
100
|
+
throw new Error(`The client has no field "${key}" (declared: ${have})`);
|
|
101
|
+
}
|
|
102
|
+
next.splice(at, 1);
|
|
103
|
+
}
|
|
104
|
+
for (const field of adds) {
|
|
105
|
+
const at = next.findIndex(f => f.key === field.key);
|
|
106
|
+
if (at >= 0)
|
|
107
|
+
next[at] = field;
|
|
108
|
+
else
|
|
109
|
+
next.push(field);
|
|
110
|
+
}
|
|
111
|
+
if (next.length > 20) {
|
|
112
|
+
throw new Error(`A client declares at most 20 fields (this would be ${next.length})`);
|
|
113
|
+
}
|
|
114
|
+
return next;
|
|
115
|
+
};
|
|
116
|
+
/** One line per field, for `clients get` and the update summary. */
|
|
117
|
+
const format_field = (f) => f.type === 'enum'
|
|
118
|
+
? `${f.key} (enum: ${(f.options ?? []).join(', ')})`
|
|
119
|
+
: `${f.key} (string${f.max_length ? `, max ${f.max_length}` : ''})`;
|
|
120
|
+
|
|
121
|
+
export { apply_field_changes, format_field, parse_field_spec, parse_fields_json };
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { withAuthHints, requireAuthAdmin } from '../../../api/auth_admin.js';
|
|
2
|
+
import { log } from '../../../log.js';
|
|
3
|
+
import { tenant_options } from '../options.js';
|
|
4
|
+
import { json_option, print_json } from '../../../lib/listing.js';
|
|
5
|
+
import { parse_field_spec, parse_fields_json, apply_field_changes, format_field } from './profile_fields.js';
|
|
6
|
+
import { resolve_client } from './resolve.js';
|
|
7
|
+
|
|
8
|
+
// The update route replaces each list whole, so every list change here is a
|
|
9
|
+
// read-modify-write of what the client has now: adding a callback never drops
|
|
10
|
+
// the others.
|
|
11
|
+
const edit_list = (current, add = [], remove = []) => {
|
|
12
|
+
if (!add.length && !remove.length)
|
|
13
|
+
return undefined;
|
|
14
|
+
const next = (current ?? []).filter(u => !remove.includes(u));
|
|
15
|
+
for (const u of add)
|
|
16
|
+
if (!next.includes(u))
|
|
17
|
+
next.push(u);
|
|
18
|
+
return next;
|
|
19
|
+
};
|
|
20
|
+
const clients_update = {
|
|
21
|
+
command: 'update <client_id>',
|
|
22
|
+
describe: 'Update an OAuth client (name, URLs, profile fields)',
|
|
23
|
+
builder: yargs => json_option(tenant_options(yargs))
|
|
24
|
+
.positional('client_id', {
|
|
25
|
+
type: 'string',
|
|
26
|
+
demandOption: true,
|
|
27
|
+
description: 'Client resource id or OAuth client_id'
|
|
28
|
+
})
|
|
29
|
+
.option('name', {
|
|
30
|
+
alias: 'n',
|
|
31
|
+
type: 'string',
|
|
32
|
+
description: 'New client name'
|
|
33
|
+
})
|
|
34
|
+
.option('description', {
|
|
35
|
+
alias: 'd',
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'New client description'
|
|
38
|
+
})
|
|
39
|
+
.option('add-callback', {
|
|
40
|
+
type: 'string',
|
|
41
|
+
array: true,
|
|
42
|
+
description: 'Add an allowed callback (redirect) URL — repeatable'
|
|
43
|
+
})
|
|
44
|
+
.option('remove-callback', {
|
|
45
|
+
type: 'string',
|
|
46
|
+
array: true,
|
|
47
|
+
description: 'Remove an allowed callback URL — repeatable'
|
|
48
|
+
})
|
|
49
|
+
.option('add-logout-url', {
|
|
50
|
+
type: 'string',
|
|
51
|
+
array: true,
|
|
52
|
+
description: 'Add an allowed post-logout URL — repeatable'
|
|
53
|
+
})
|
|
54
|
+
.option('remove-logout-url', {
|
|
55
|
+
type: 'string',
|
|
56
|
+
array: true,
|
|
57
|
+
description: 'Remove an allowed post-logout URL — repeatable'
|
|
58
|
+
})
|
|
59
|
+
.option('add-web-origin', {
|
|
60
|
+
type: 'string',
|
|
61
|
+
array: true,
|
|
62
|
+
description: 'Add an allowed web origin (CORS) — repeatable'
|
|
63
|
+
})
|
|
64
|
+
.option('remove-web-origin', {
|
|
65
|
+
type: 'string',
|
|
66
|
+
array: true,
|
|
67
|
+
description: 'Remove an allowed web origin — repeatable'
|
|
68
|
+
})
|
|
69
|
+
.option('add-field', {
|
|
70
|
+
type: 'string',
|
|
71
|
+
array: true,
|
|
72
|
+
description: 'Declare a profile field users may write with PATCH /me: key, key:string[:max_length] or key:enum:a,b,c. Re-adding a key replaces it — repeatable'
|
|
73
|
+
})
|
|
74
|
+
.option('remove-field', {
|
|
75
|
+
type: 'string',
|
|
76
|
+
array: true,
|
|
77
|
+
description: 'Remove a declared profile field by key — repeatable'
|
|
78
|
+
})
|
|
79
|
+
.option('fields-json', {
|
|
80
|
+
type: 'string',
|
|
81
|
+
description: 'Replace the whole profile field list with a JSON array of {key, type, options?, max_length?}'
|
|
82
|
+
})
|
|
83
|
+
.option('inherit-fields', {
|
|
84
|
+
type: 'boolean',
|
|
85
|
+
description: "Drop this client's own profile field list and use the tenant's again"
|
|
86
|
+
})
|
|
87
|
+
.conflicts('fields-json', ['add-field', 'remove-field', 'inherit-fields'])
|
|
88
|
+
.conflicts('inherit-fields', ['add-field', 'remove-field'])
|
|
89
|
+
.example('$0 auth clients update my-app --add-field profile:enum:student,teacher', 'Let users of my-app pick their profile with auth-js updateProfile')
|
|
90
|
+
.example('$0 auth clients update my-app --remove-field school', 'Stop accepting a field')
|
|
91
|
+
.example('$0 auth clients update my-app --add-callback https://app.example.com/cb', 'Allow one more redirect URL, keeping the others')
|
|
92
|
+
.showHelpOnFail(false),
|
|
93
|
+
handler: withAuthHints(async (args) => {
|
|
94
|
+
// Parse everything before any request, so a bad spec costs nothing.
|
|
95
|
+
const adds = (args.addField ?? []).map(parse_field_spec);
|
|
96
|
+
const full = args.fieldsJson
|
|
97
|
+
? parse_fields_json(args.fieldsJson)
|
|
98
|
+
: undefined;
|
|
99
|
+
const api = await requireAuthAdmin(args);
|
|
100
|
+
const client = await resolve_client(api, args.client_id);
|
|
101
|
+
const own = client.profile_fields;
|
|
102
|
+
let profile_fields;
|
|
103
|
+
if (full)
|
|
104
|
+
profile_fields = full;
|
|
105
|
+
else if (args.inheritFields)
|
|
106
|
+
profile_fields = null;
|
|
107
|
+
else if (adds.length || args.removeField?.length) {
|
|
108
|
+
profile_fields = apply_field_changes(own, adds, args.removeField ?? []);
|
|
109
|
+
}
|
|
110
|
+
const callbacks = edit_list(client.callbacks, args.addCallback, args.removeCallback);
|
|
111
|
+
const logout_urls = edit_list(client.logout_urls, args.addLogoutUrl, args.removeLogoutUrl);
|
|
112
|
+
const web_origins = edit_list(client.web_origins, args.addWebOrigin, args.removeWebOrigin);
|
|
113
|
+
const patch = {
|
|
114
|
+
...(args.name !== undefined ? { name: args.name } : {}),
|
|
115
|
+
...(args.description !== undefined
|
|
116
|
+
? { description: args.description }
|
|
117
|
+
: {}),
|
|
118
|
+
...(callbacks ? { callbacks } : {}),
|
|
119
|
+
...(logout_urls ? { logout_urls } : {}),
|
|
120
|
+
...(web_origins ? { web_origins } : {}),
|
|
121
|
+
...(profile_fields !== undefined ? { profile_fields } : {})
|
|
122
|
+
};
|
|
123
|
+
if (Object.keys(patch).length === 0) {
|
|
124
|
+
throw new Error('Nothing to update — pass a --name, --description, an --add-*/--remove-* option, --fields-json or --inherit-fields');
|
|
125
|
+
}
|
|
126
|
+
const updated = await api.clientUpdate(client.id, patch);
|
|
127
|
+
if (args.json)
|
|
128
|
+
return print_json(updated);
|
|
129
|
+
log.info(`✅ Updated client "${updated.name}" (${updated.client_id})`);
|
|
130
|
+
if (profile_fields !== undefined) {
|
|
131
|
+
const now = updated.profile_fields;
|
|
132
|
+
if (!now) {
|
|
133
|
+
log.info(' Profile fields: inherited from the tenant');
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
log.info(` Profile fields: ${now.length ? now.map(format_field).join(', ') : 'none'}`);
|
|
137
|
+
// The first own list silently stops inheriting the tenant's: say so.
|
|
138
|
+
if (!own) {
|
|
139
|
+
log.info(" ℹ️ This client now has its own list, which replaces the tenant's for it. --inherit-fields undoes that.");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export { clients_update };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import { version } from '../../config.js';
|
|
3
3
|
import { isDevBuild, getLatestVersion, isNewerVersion, CLI_PACKAGE } from '../../lib/UpdateChecker.js';
|
|
4
|
-
import {
|
|
4
|
+
import { upgradeTo, npmDeps } from '../../lib/self_upgrade.js';
|
|
5
5
|
import { log } from '../../log.js';
|
|
6
6
|
|
|
7
7
|
const upgrade = {
|
|
@@ -22,15 +22,25 @@ const upgrade = {
|
|
|
22
22
|
return;
|
|
23
23
|
}
|
|
24
24
|
const spinner = ora(`Upgrading ${CLI_PACKAGE} ${version} → ${latest}`).start();
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
const res = await upgradeTo(latest, npmDeps, {
|
|
26
|
+
onRetry: () => {
|
|
27
|
+
spinner.text = `${latest} was just published and the npm registry is still propagating it — retrying…`;
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
if (res.ok) {
|
|
27
31
|
spinner.succeed(`Upgraded to ${latest}`);
|
|
32
|
+
return;
|
|
28
33
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
log.error(`❌
|
|
32
|
-
|
|
34
|
+
spinner.fail("Upgrade failed");
|
|
35
|
+
if (res.reason === "not_propagated") {
|
|
36
|
+
log.error(`❌ ${latest} is not available from the npm registry yet (still on ${res.installed ?? version}). A release takes a few minutes to propagate — run \`faable upgrade\` again shortly.`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
if (res.output)
|
|
40
|
+
log.error(res.output.trim());
|
|
41
|
+
log.error(`❌ Could not upgrade automatically. Try manually: npm install -g ${CLI_PACKAGE}@${latest}`);
|
|
33
42
|
}
|
|
43
|
+
process.exit(1);
|
|
34
44
|
},
|
|
35
45
|
};
|
|
36
46
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { execFile } from 'child_process';
|
|
2
|
+
import { CLI_PACKAGE } from './UpdateChecker.js';
|
|
3
|
+
|
|
4
|
+
// npm's ways of saying "that version does not exist (here, yet)".
|
|
5
|
+
const NOT_FOUND = /ETARGET|notarget|No matching version|E404/i;
|
|
6
|
+
const upgradeTo = async (target, deps, { attempts = 5, wait_ms = 30_000, onRetry = (_n) => { } } = {}) => {
|
|
7
|
+
let output = "";
|
|
8
|
+
for (let n = 1; n <= attempts; n++) {
|
|
9
|
+
const res = await deps.install(target);
|
|
10
|
+
output = res.output;
|
|
11
|
+
if (res.ok) {
|
|
12
|
+
const installed = await deps.installed();
|
|
13
|
+
if (installed === target)
|
|
14
|
+
return { ok: true, attempts: n };
|
|
15
|
+
// npm said yes and changed nothing: the same lag, from another angle.
|
|
16
|
+
}
|
|
17
|
+
else if (!NOT_FOUND.test(res.output)) {
|
|
18
|
+
return { ok: false, reason: "install_failed", output };
|
|
19
|
+
}
|
|
20
|
+
if (n < attempts) {
|
|
21
|
+
onRetry(n);
|
|
22
|
+
await deps.sleep(wait_ms);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
ok: false,
|
|
27
|
+
reason: "not_propagated",
|
|
28
|
+
output,
|
|
29
|
+
installed: await deps.installed(),
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
const run = (file, args, timeout) => new Promise((resolve) => {
|
|
33
|
+
execFile(file, args, { timeout, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => resolve({ ok: !err, output: `${stdout ?? ""}${stderr ?? ""}` }));
|
|
34
|
+
});
|
|
35
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
36
|
+
/** The real thing: global npm. */
|
|
37
|
+
const npmDeps = {
|
|
38
|
+
install: (version) => run(npm, ["install", "-g", "--prefer-online", `${CLI_PACKAGE}@${version}`], 180_000),
|
|
39
|
+
installed: async () => {
|
|
40
|
+
const res = await run(npm, ["ls", "-g", "--json", "--depth=0", CLI_PACKAGE], 30_000);
|
|
41
|
+
try {
|
|
42
|
+
return JSON.parse(res.output).dependencies?.[CLI_PACKAGE]?.version;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export { npmDeps, upgradeTo };
|
package/dist/mcp/tools.js
CHANGED
|
@@ -900,6 +900,53 @@ const TOOLS = [
|
|
|
900
900
|
annotations: AUTH_WRITE,
|
|
901
901
|
example: { user: 'ana@example.com' }
|
|
902
902
|
},
|
|
903
|
+
{
|
|
904
|
+
name: 'update_auth_client_fields',
|
|
905
|
+
title: "Change an app's profile fields",
|
|
906
|
+
description: "Declare which custom profile fields the users of one Auth application may write on themselves (auth-js `updateProfile`, stored in user_metadata[\"faable.com/fields\"]). `add` takes `key`, `key:string:120` or `key:enum:a,b,c`; re-adding a key replaces it. `inherit: true` drops the app's own list and uses the tenant's again. An app's own list replaces the tenant's for that app. Only the fields: the app's URLs and settings are not changed here.",
|
|
907
|
+
input: {
|
|
908
|
+
client: z
|
|
909
|
+
.string()
|
|
910
|
+
.describe('The application: client resource id (client_…) or OAuth client_id'),
|
|
911
|
+
add: z
|
|
912
|
+
.array(z.string())
|
|
913
|
+
.max(20)
|
|
914
|
+
.optional()
|
|
915
|
+
.describe('Fields to declare: key, key:string[:max_length] or key:enum:a,b,c'),
|
|
916
|
+
remove: z
|
|
917
|
+
.array(z.string())
|
|
918
|
+
.max(20)
|
|
919
|
+
.optional()
|
|
920
|
+
.describe('Field keys to stop accepting'),
|
|
921
|
+
inherit: z
|
|
922
|
+
.boolean()
|
|
923
|
+
.optional()
|
|
924
|
+
.describe("Drop the app's own list and use the tenant's"),
|
|
925
|
+
tenant,
|
|
926
|
+
project
|
|
927
|
+
},
|
|
928
|
+
command: ['auth', 'clients', 'update'],
|
|
929
|
+
flags: a => [
|
|
930
|
+
a.client,
|
|
931
|
+
...(a.add ?? []).flatMap((f) => ['--add-field', f]),
|
|
932
|
+
...(a.remove ?? []).flatMap((k) => ['--remove-field', k]),
|
|
933
|
+
...(a.inherit ? ['--inherit-fields'] : []),
|
|
934
|
+
...auth_scope(a)
|
|
935
|
+
],
|
|
936
|
+
shape: (c) => ({
|
|
937
|
+
id: c.id,
|
|
938
|
+
client_id: c.client_id,
|
|
939
|
+
name: c.name,
|
|
940
|
+
profile_fields: c.profile_fields ?? null
|
|
941
|
+
}),
|
|
942
|
+
write: true,
|
|
943
|
+
annotations: AUTH_WRITE,
|
|
944
|
+
example: {
|
|
945
|
+
client: 'client_1',
|
|
946
|
+
add: ['profile:enum:student,teacher'],
|
|
947
|
+
remove: ['school']
|
|
948
|
+
}
|
|
949
|
+
},
|
|
903
950
|
{
|
|
904
951
|
name: 'send_password_setup',
|
|
905
952
|
title: 'Send a password setup email',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@faable/faable",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.9.1",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Marc Pomar <marc@faable.com>",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"@actions/core": "^3.0.0",
|
|
30
|
-
"@faable/auth-sdk": "^2.7.
|
|
30
|
+
"@faable/auth-sdk": "^2.7.105",
|
|
31
31
|
"@modelcontextprotocol/sdk": "^1.31.0",
|
|
32
32
|
"axios": "^1.20.0",
|
|
33
33
|
"fs-extra": "^11.3.2",
|