auth0-deploy-cli 9.0.0-beta.2 → 9.1.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/AGENTS.md +2 -357
- package/CHANGELOG.md +185 -10
- package/CLAUDE.md +138 -2
- package/README.md +1 -1
- package/lib/context/defaults.d.ts +7 -0
- package/lib/context/defaults.js +74 -30
- package/lib/context/directory/handlers/actionModules.js +1 -4
- package/lib/context/directory/handlers/actions.js +1 -4
- package/lib/context/directory/handlers/attackProtection.js +11 -1
- package/lib/context/directory/handlers/branding.js +4 -1
- package/lib/context/directory/handlers/clientAuthCredentials.d.ts +5 -0
- package/lib/context/directory/handlers/clientAuthCredentials.js +13 -0
- package/lib/context/directory/handlers/clientGrants.js +78 -24
- package/lib/context/directory/handlers/clients.js +20 -3
- package/lib/context/directory/handlers/connections.js +19 -5
- package/lib/context/directory/handlers/databases.js +1 -4
- package/lib/context/directory/handlers/emailTemplates.js +5 -0
- package/lib/context/directory/handlers/guardianEmailFactorSettings.d.ts +5 -0
- package/lib/context/directory/handlers/guardianEmailFactorSettings.js +38 -0
- package/lib/context/directory/handlers/guardianPhoneFactorSettings.d.ts +5 -0
- package/lib/context/directory/handlers/guardianPhoneFactorSettings.js +38 -0
- package/lib/context/directory/handlers/guardianSettings.d.ts +5 -0
- package/lib/context/directory/handlers/guardianSettings.js +38 -0
- package/lib/context/directory/handlers/hooks.js +1 -4
- package/lib/context/directory/handlers/index.js +12 -0
- package/lib/context/directory/handlers/networkACLKeys.d.ts +6 -0
- package/lib/context/directory/handlers/networkACLKeys.js +54 -0
- package/lib/context/directory/handlers/prompts.js +7 -3
- package/lib/context/directory/handlers/rateLimitPolicies.d.ts +6 -0
- package/lib/context/directory/handlers/rateLimitPolicies.js +53 -0
- package/lib/context/directory/handlers/rules.js +1 -4
- package/lib/context/directory/index.js +1 -4
- package/lib/context/index.js +32 -31
- package/lib/context/yaml/handlers/attackProtection.js +5 -2
- package/lib/context/yaml/handlers/branding.js +4 -1
- package/lib/context/yaml/handlers/clientAuthCredentials.d.ts +5 -0
- package/lib/context/yaml/handlers/clientAuthCredentials.js +13 -0
- package/lib/context/yaml/handlers/clients.js +19 -1
- package/lib/context/yaml/handlers/connections.js +8 -1
- package/lib/context/yaml/handlers/flows.js +3 -0
- package/lib/context/yaml/handlers/forms.js +3 -0
- package/lib/context/yaml/handlers/guardianEmailFactorSettings.d.ts +5 -0
- package/lib/context/yaml/handlers/guardianEmailFactorSettings.js +15 -0
- package/lib/context/yaml/handlers/guardianPhoneFactorSettings.d.ts +5 -0
- package/lib/context/yaml/handlers/guardianPhoneFactorSettings.js +15 -0
- package/lib/context/yaml/handlers/guardianSettings.d.ts +5 -0
- package/lib/context/yaml/handlers/guardianSettings.js +15 -0
- package/lib/context/yaml/handlers/index.js +12 -0
- package/lib/context/yaml/handlers/networkACLKeys.d.ts +6 -0
- package/lib/context/yaml/handlers/networkACLKeys.js +42 -0
- package/lib/context/yaml/handlers/prompts.js +3 -1
- package/lib/context/yaml/handlers/rateLimitPolicies.d.ts +6 -0
- package/lib/context/yaml/handlers/rateLimitPolicies.js +27 -0
- package/lib/context/yaml/index.js +85 -7
- package/lib/keywordPreservation.d.ts +1 -1
- package/lib/keywordPreservation.js +2 -1
- package/lib/readonly.js +5 -0
- package/lib/tools/auth0/handlers/attackProtection.d.ts +12 -0
- package/lib/tools/auth0/handlers/attackProtection.js +66 -6
- package/lib/tools/auth0/handlers/clientAuthCredentials.d.ts +27 -0
- package/lib/tools/auth0/handlers/clientAuthCredentials.js +234 -0
- package/lib/tools/auth0/handlers/clientAuthCredentialsPre.d.ts +38 -0
- package/lib/tools/auth0/handlers/clientAuthCredentialsPre.js +123 -0
- package/lib/tools/auth0/handlers/clientGrants.d.ts +1 -1
- package/lib/tools/auth0/handlers/clients.d.ts +106 -0
- package/lib/tools/auth0/handlers/clients.js +207 -3
- package/lib/tools/auth0/handlers/connectionProfiles.d.ts +27 -0
- package/lib/tools/auth0/handlers/connectionProfiles.js +30 -0
- package/lib/tools/auth0/handlers/connections.d.ts +34 -6
- package/lib/tools/auth0/handlers/connections.js +75 -11
- package/lib/tools/auth0/handlers/databases.d.ts +28 -0
- package/lib/tools/auth0/handlers/databases.js +58 -24
- package/lib/tools/auth0/handlers/default.d.ts +23 -0
- package/lib/tools/auth0/handlers/default.js +47 -29
- package/lib/tools/auth0/handlers/eventStreams.js +10 -2
- package/lib/tools/auth0/handlers/guardianEmailFactorSettings.d.ts +22 -0
- package/lib/tools/auth0/handlers/guardianEmailFactorSettings.js +77 -0
- package/lib/tools/auth0/handlers/guardianFactorTemplates.js +13 -4
- package/lib/tools/auth0/handlers/guardianPhoneFactorMessageTypes.js +11 -9
- package/lib/tools/auth0/handlers/guardianPhoneFactorSelectedProvider.js +11 -9
- package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.d.ts +22 -0
- package/lib/tools/auth0/handlers/guardianPhoneFactorSettings.js +77 -0
- package/lib/tools/auth0/handlers/guardianPolicies.js +11 -1
- package/lib/tools/auth0/handlers/guardianSettings.d.ts +30 -0
- package/lib/tools/auth0/handlers/guardianSettings.js +85 -0
- package/lib/tools/auth0/handlers/hooks.js +1 -1
- package/lib/tools/auth0/handlers/index.js +16 -0
- package/lib/tools/auth0/handlers/networkACLKeys.d.ts +45 -0
- package/lib/tools/auth0/handlers/networkACLKeys.js +177 -0
- package/lib/tools/auth0/handlers/networkACLs.d.ts +132 -2
- package/lib/tools/auth0/handlers/networkACLs.js +100 -16
- package/lib/tools/auth0/handlers/organizations.d.ts +33 -1
- package/lib/tools/auth0/handlers/organizations.js +177 -19
- package/lib/tools/auth0/handlers/phoneProvider.js +10 -2
- package/lib/tools/auth0/handlers/phoneTemplates.d.ts +4 -1
- package/lib/tools/auth0/handlers/phoneTemplates.js +67 -10
- package/lib/tools/auth0/handlers/prompts.d.ts +17 -0
- package/lib/tools/auth0/handlers/prompts.js +47 -14
- package/lib/tools/auth0/handlers/rateLimitPolicies.d.ts +81 -0
- package/lib/tools/auth0/handlers/rateLimitPolicies.js +203 -0
- package/lib/tools/auth0/handlers/resourceServers.d.ts +48 -0
- package/lib/tools/auth0/handlers/resourceServers.js +56 -2
- package/lib/tools/auth0/handlers/riskAssessment.d.ts +1 -1
- package/lib/tools/auth0/handlers/riskAssessment.js +19 -1
- package/lib/tools/auth0/handlers/roles.d.ts +4 -0
- package/lib/tools/auth0/handlers/roles.js +27 -9
- package/lib/tools/auth0/handlers/rules.js +19 -7
- package/lib/tools/auth0/handlers/scimHandler.js +8 -3
- package/lib/tools/auth0/handlers/tenant.d.ts +45 -0
- package/lib/tools/auth0/handlers/tenant.js +48 -0
- package/lib/tools/auth0/handlers/themes.d.ts +37 -1
- package/lib/tools/auth0/handlers/themes.js +64 -0
- package/lib/tools/auth0/handlers/tokenExchangeProfiles.js +3 -1
- package/lib/tools/auth0/index.js +12 -6
- package/lib/tools/calculateDryRunChanges.js +12 -8
- package/lib/tools/constants.d.ts +3 -0
- package/lib/tools/constants.js +8 -0
- package/lib/tools/utils.d.ts +3 -0
- package/lib/tools/utils.js +41 -2
- package/lib/types.d.ts +10 -1
- package/lib/utils.d.ts +6 -0
- package/lib/utils.js +17 -2
- package/package.json +2 -2
- package/references/code-style.md +133 -0
- package/references/commands.md +98 -0
- package/references/docs-update.md +34 -0
- package/references/git-workflow.md +135 -0
- package/references/pitfalls.md +62 -0
- package/references/testing.md +118 -0
package/CLAUDE.md
CHANGED
|
@@ -1,3 +1,139 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AI Agent Guidelines for auth0-deploy-cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## Your Role
|
|
4
|
+
|
|
5
|
+
You are a TypeScript CLI engineer maintaining auth0-deploy-cli — a bidirectional sync tool for Auth0 tenant configurations. You write resource handlers, context parsers, and change-detection logic.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Project Structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
auth0-deploy-cli/
|
|
13
|
+
├── src/ # TypeScript source
|
|
14
|
+
│ ├── index.ts # CLI entry point (export + import commands)
|
|
15
|
+
│ ├── commands/ # import.ts, export.ts — command wiring
|
|
16
|
+
│ ├── configFactory.ts # Configuration loading and validation
|
|
17
|
+
│ ├── types.ts # Shared TypeScript interfaces
|
|
18
|
+
│ ├── context/
|
|
19
|
+
│ │ ├── directory/ # Directory-format parser + keyword replacement
|
|
20
|
+
│ │ └── yaml/ # YAML-format parser + keyword replacement
|
|
21
|
+
│ └── tools/
|
|
22
|
+
│ ├── deploy.ts # Main deployment orchestrator
|
|
23
|
+
│ ├── calculateChanges.ts # Change detection (create / update / delete)
|
|
24
|
+
│ ├── constants.ts # Supported resource type registry
|
|
25
|
+
│ ├── validationError.ts # ValidationError class
|
|
26
|
+
│ └── auth0/
|
|
27
|
+
│ ├── client.ts # Management API client wrapper
|
|
28
|
+
│ ├── handlers/ # 49 resource-specific handlers (schemas embedded per handler)
|
|
29
|
+
│ └── schema.ts # AJV schema aggregator (compiled from handler schemas)
|
|
30
|
+
├── test/ # Mirrors src/
|
|
31
|
+
│ ├── tools/auth0/handlers/ # Handler unit tests
|
|
32
|
+
│ ├── context/ # Context parser tests
|
|
33
|
+
│ └── e2e/ # E2E tests (require real Auth0 tenant)
|
|
34
|
+
├── examples/
|
|
35
|
+
│ ├── directory/ # Sample directory-format config
|
|
36
|
+
│ └── yaml/ # Sample YAML-format config
|
|
37
|
+
├── docs/ # Extended documentation
|
|
38
|
+
├── .circleci/config.yml # CI: lint → unit tests → publish
|
|
39
|
+
└── package.json # npm scripts and dependencies
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Boundaries
|
|
45
|
+
|
|
46
|
+
### ✅ Always Do
|
|
47
|
+
|
|
48
|
+
- Run `npm run build && npm test && npm run lint` before committing
|
|
49
|
+
- Make surgical changes — touch only what the request requires; do not refactor or reformat adjacent code that isn't broken
|
|
50
|
+
- Support both YAML and directory context formats when implementing or changing resource handling
|
|
51
|
+
- Preserve `@@KEY@@` (JSON-stringified / array values) and `##KEY##` (literal string) keyword replacement in every new field that flows through export/import
|
|
52
|
+
- Strip write-only fields (secrets, key material) on export; exclude read-only API-generated fields (`created_at`, `updated_at`, `id`, `fingerprint`) from create/update payloads
|
|
53
|
+
- Use `name` (or a stable name-like field) as the primary entry in a handler's `identifiers[]` array — never solely an auto-generated UUID `id`
|
|
54
|
+
- Place new handlers in `@order()` after all their dependencies
|
|
55
|
+
- Add unit tests for new logic; test both the happy path and error paths
|
|
56
|
+
- Update `README.md` and `examples/` in the same PR when changing CLI commands, flags, output formats, or supported resource types
|
|
57
|
+
|
|
58
|
+
### ⚠️ Ask First
|
|
59
|
+
|
|
60
|
+
- Any breaking change — never break backward compatibility on your own initiative; stop and ask first
|
|
61
|
+
- Adding or bumping dependencies in `package.json`
|
|
62
|
+
- Modifying public CLI commands, subcommands, or flag names
|
|
63
|
+
- Changes to `.circleci/config.yml`
|
|
64
|
+
- Modifying keyword replacement logic or the export/import schema
|
|
65
|
+
- Running E2E tests — they hit a real Auth0 tenant and can mutate resources (see [references/testing.md](references/testing.md))
|
|
66
|
+
- **EA / entitlement-gated features**: Before marking complete — document every code path you could not test end-to-end, spell out the exact API shape assumptions your code relies on, verify that the list endpoint returns the fields read from `this.existing`, and create a follow-up ticket to re-verify once entitlement is available
|
|
67
|
+
|
|
68
|
+
### 🚫 Never Do
|
|
69
|
+
|
|
70
|
+
- Commit secrets, API keys, Auth0 credentials, or PEM keys
|
|
71
|
+
- Mutate state on the dry-run path — `processChanges()` must be a complete no-op during dry run on every affected path
|
|
72
|
+
- Use an auto-generated UUID as the sole entry in `identifiers[]`; always include `name` as the primary matching key
|
|
73
|
+
- Log or export write-only fields (secrets, key material, tokens)
|
|
74
|
+
- Add `additionalProperties: false` to a schema for an EA-gated object without confirming the full API response shape from a real tenant response
|
|
75
|
+
- Modify `node_modules/`, `lib/`, or other build-output directories by hand
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Security Considerations
|
|
80
|
+
|
|
81
|
+
- Auth0 credentials (`AUTH0_DOMAIN`, `AUTH0_CLIENT_ID`, `AUTH0_CLIENT_SECRET`) are never committed; `config*.json` and `.env` are gitignored
|
|
82
|
+
- Write-only fields (secrets, signing keys, PEM material) are stripped on export and never written to disk
|
|
83
|
+
- Delete operations require explicit `AUTH0_ALLOW_DELETE=true`; absent this flag, the handler silently skips deletes
|
|
84
|
+
- Resource identifiers are sanitized before use in API calls
|
|
85
|
+
- No telemetry in this repo — deploy-cli delegates all Auth0 API calls to the `auth0` npm SDK, which handles its own telemetry
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
> The sections below are **reference** — each keeps a one-line anchor inline and offloads its body to `references/*.md`.
|
|
90
|
+
|
|
91
|
+
## Commands
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
npm run build # compile TypeScript → lib/
|
|
95
|
+
npm test # unit tests (safe — no credentials required)
|
|
96
|
+
npm run lint # eslint + kacl changelog lint
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
See [references/commands.md](references/commands.md) for the full command reference including E2E tests, watch mode, and direct CLI invocation. Read when you need to build, test, or run the CLI.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Testing
|
|
104
|
+
|
|
105
|
+
Unit tests (`npm test`) are safe — no credentials required. E2E tests require a real Auth0 tenant; ask before running (see Boundaries).
|
|
106
|
+
|
|
107
|
+
See [references/testing.md](references/testing.md) for test conventions, sinon mock patterns, handler test scaffolding, and E2E requirements. Read when writing or debugging tests.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Code Style
|
|
112
|
+
|
|
113
|
+
Single quotes, semicolons required, `no-unused-vars` — all three fail CI via ESLint (`eslint.config.js`). TypeScript strict mode is off; do not enable without asking first.
|
|
114
|
+
|
|
115
|
+
See [references/code-style.md](references/code-style.md) for naming conventions, handler implementation patterns, and good/bad examples. Read when adding a new handler or context parser.
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Git Workflow
|
|
120
|
+
|
|
121
|
+
Conventional Commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`). Branch naming: `DXCDT-XXXX` (Jira ticket number).
|
|
122
|
+
|
|
123
|
+
See [references/git-workflow.md](references/git-workflow.md) for PR conventions and the full PR review checklist. Read before creating or reviewing a PR.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Common Pitfalls
|
|
128
|
+
|
|
129
|
+
See [references/pitfalls.md](references/pitfalls.md). Read before implementing a new handler or touching keyword replacement, dry-run, EA-gated features, or the export/import roundtrip.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Docs Update Rules
|
|
134
|
+
|
|
135
|
+
`README.md` — present. `EXAMPLES.md` — missing (create when adding a new integration pattern or command example). `examples/` — present (yaml/ and directory/ samples).
|
|
136
|
+
|
|
137
|
+
> Update `README.md` and `examples/` in the same PR when changing CLI commands, flags, output formats, or supported resource types. Do not defer.
|
|
138
|
+
|
|
139
|
+
See [references/docs-update.md](references/docs-update.md) for the full tracked-docs inventory and code-to-docs mapping table. Read when adding or changing commands, flags, or resource types.
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
The Auth0 Deploy CLI is a tool that helps you manage your Auth0 tenant configuration. It integrates into your development workflows as a standalone CLI or as a node module.
|
|
10
10
|
|
|
11
|
-
**Supported resource types:** actions, branding, client grants, clients (applications), connections, databases, custom domains, email templates, emails, grants, guardian, hook secrets, log streams, organizations, pages, prompts, resource servers (APIs), roles, tenant settings, themes, forms, flows, self-service profiles, network ACLs.
|
|
11
|
+
**Supported resource types:** actions, branding, client grants, clients (applications), connections, databases, custom domains, email templates, emails, grants, guardian, hook secrets, log streams, organizations, pages, prompts, resource servers (APIs), roles, tenant settings, themes, forms, flows, self-service profiles, network ACLs, network ACL keys.
|
|
12
12
|
|
|
13
13
|
🎢 [Highlights](#highlights) • 📚 [Documentation](#documentation) • 🚀 [Getting Started](#getting-started) • 📝 [Feedback](#feedback) • 💬 [Discussions](https://github.com/auth0/auth0-deploy-cli/discussions)
|
|
14
14
|
|
|
@@ -6,4 +6,11 @@ export declare function phoneTemplatesDefaults(phoneTemplate: any): any;
|
|
|
6
6
|
export declare function connectionDefaults(connection: any, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any;
|
|
7
7
|
export declare function logStreamDefaults(logStreams: any, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any;
|
|
8
8
|
export declare function eventStreamDefaults(eventStreams: any[], config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): any[];
|
|
9
|
+
/**
|
|
10
|
+
* Sorts primitive arrays (e.g. `shields`, `admin_notification_frequency`) within the
|
|
11
|
+
* attackProtection subtree in place, so repeated exports produce deterministic output.
|
|
12
|
+
* These are unordered, string-typed enum sets — ordering is cosmetic. Object arrays are
|
|
13
|
+
* left as-is.
|
|
14
|
+
*/
|
|
15
|
+
export declare function sortAttackProtectionArrays(attackProtection: AttackProtection): AttackProtection;
|
|
9
16
|
export declare function attackProtectionDefaults(attackProtection: AttackProtection, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): AttackProtection;
|
package/lib/context/defaults.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.phoneTemplatesDefaults = phoneTemplatesDefaults;
|
|
|
6
6
|
exports.connectionDefaults = connectionDefaults;
|
|
7
7
|
exports.logStreamDefaults = logStreamDefaults;
|
|
8
8
|
exports.eventStreamDefaults = eventStreamDefaults;
|
|
9
|
+
exports.sortAttackProtectionArrays = sortAttackProtectionArrays;
|
|
9
10
|
exports.attackProtectionDefaults = attackProtectionDefaults;
|
|
10
11
|
const utils_1 = require("../tools/utils");
|
|
11
12
|
// env vars arrive as strings, so check both forms
|
|
@@ -22,45 +23,57 @@ function emailProviderDefaults(emailProvider, config) {
|
|
|
22
23
|
// Add placeholder for credentials as they cannot be exported
|
|
23
24
|
const { name } = updated;
|
|
24
25
|
if (apiKeyProviders.includes(name)) {
|
|
25
|
-
updated.credentials
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
if (typeof updated.credentials !== 'string') {
|
|
27
|
+
updated.credentials = {
|
|
28
|
+
api_key: `##${name.toUpperCase()}_API_KEY##`,
|
|
29
|
+
...(updated.credentials || {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
29
32
|
}
|
|
30
33
|
if (name === 'smtp') {
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
// If credentials is a keyword placeholder string (e.g. @@SMTP_CREDENTIALS@@), preserve it as-is.
|
|
35
|
+
// The `in` operator requires an object and would throw a TypeError on a string.
|
|
36
|
+
if (typeof updated.credentials !== 'string') {
|
|
37
|
+
// This is to mask smtp_user to '##SMTP_USER##'
|
|
38
|
+
if (updated.credentials && 'smtp_user' in updated.credentials) {
|
|
39
|
+
delete updated.credentials.smtp_user;
|
|
40
|
+
}
|
|
41
|
+
updated.credentials = {
|
|
42
|
+
smtp_host: '##SMTP_HOSTNAME##',
|
|
43
|
+
smtp_port: '##SMTP_PORT##',
|
|
44
|
+
smtp_user: '##SMTP_USER##',
|
|
45
|
+
smtp_pass: '##SMTP_PASS##',
|
|
46
|
+
...(updated.credentials || {}),
|
|
47
|
+
};
|
|
34
48
|
}
|
|
35
|
-
updated.credentials = {
|
|
36
|
-
smtp_host: '##SMTP_HOSTNAME##',
|
|
37
|
-
smtp_port: '##SMTP_PORT##',
|
|
38
|
-
smtp_user: '##SMTP_USER##',
|
|
39
|
-
smtp_pass: '##SMTP_PASS##',
|
|
40
|
-
...(updated.credentials || {}),
|
|
41
|
-
};
|
|
42
49
|
}
|
|
43
50
|
if (name === 'ses') {
|
|
44
|
-
updated.credentials
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
if (typeof updated.credentials !== 'string') {
|
|
52
|
+
updated.credentials = {
|
|
53
|
+
accessKeyId: '##SES_ACCESS_KEY_ID##',
|
|
54
|
+
secretAccessKey: '##SES_ACCESS_SECRET_KEY##',
|
|
55
|
+
region: '##SES_AWS_REGION##',
|
|
56
|
+
...(updated.credentials || {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
50
59
|
}
|
|
51
60
|
if (name === 'azure_cs') {
|
|
52
|
-
updated.credentials
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
61
|
+
if (typeof updated.credentials !== 'string') {
|
|
62
|
+
updated.credentials = {
|
|
63
|
+
connectionString: '##AZURE_CS_CONNECTION_KEY##',
|
|
64
|
+
...(updated.credentials || {}),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
56
67
|
}
|
|
57
68
|
if (name === 'ms365') {
|
|
58
|
-
updated.credentials
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
if (typeof updated.credentials !== 'string') {
|
|
70
|
+
updated.credentials = {
|
|
71
|
+
tenantId: '##MS365_TENANT_ID##',
|
|
72
|
+
clientId: '##MS365_CLIENT_ID##',
|
|
73
|
+
clientSecret: '##MS365_CLIENT_SECRET##',
|
|
74
|
+
...(updated.credentials || {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
64
77
|
}
|
|
65
78
|
return updated;
|
|
66
79
|
}
|
|
@@ -87,6 +100,12 @@ function phoneProviderDefaults(phoneProvider) {
|
|
|
87
100
|
auth_token: `##${name.toUpperCase()}_AUTH_TOKEN##`,
|
|
88
101
|
};
|
|
89
102
|
}
|
|
103
|
+
else if (name === 'custom') {
|
|
104
|
+
// The `custom` provider has no exportable secrets, but the API requires a
|
|
105
|
+
// `credentials` object on import even when empty. Emit an empty object so
|
|
106
|
+
// exported configs round-trip cleanly.
|
|
107
|
+
updated.credentials = {};
|
|
108
|
+
}
|
|
90
109
|
return updated;
|
|
91
110
|
}
|
|
92
111
|
function phoneTemplatesDefaults(phoneTemplate) {
|
|
@@ -193,6 +212,31 @@ function eventStreamDefaults(eventStreams, config) {
|
|
|
193
212
|
};
|
|
194
213
|
});
|
|
195
214
|
}
|
|
215
|
+
/**
|
|
216
|
+
* Sorts primitive arrays (e.g. `shields`, `admin_notification_frequency`) within the
|
|
217
|
+
* attackProtection subtree in place, so repeated exports produce deterministic output.
|
|
218
|
+
* These are unordered, string-typed enum sets — ordering is cosmetic. Object arrays are
|
|
219
|
+
* left as-is.
|
|
220
|
+
*/
|
|
221
|
+
function sortAttackProtectionArrays(attackProtection) {
|
|
222
|
+
const sortArraysDeep = (value) => {
|
|
223
|
+
if (Array.isArray(value)) {
|
|
224
|
+
const isPrimitiveArray = value.every((item) => item === null || typeof item !== 'object');
|
|
225
|
+
if (isPrimitiveArray) {
|
|
226
|
+
value.sort();
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
value.forEach(sortArraysDeep);
|
|
230
|
+
}
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (value && typeof value === 'object') {
|
|
234
|
+
Object.values(value).forEach(sortArraysDeep);
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
sortArraysDeep(attackProtection);
|
|
238
|
+
return attackProtection;
|
|
239
|
+
}
|
|
196
240
|
function attackProtectionDefaults(attackProtection, config) {
|
|
197
241
|
if (isExportSecrets(config))
|
|
198
242
|
return attackProtection;
|
|
@@ -24,10 +24,7 @@ function parse(context) {
|
|
|
24
24
|
const normalizedCode = module.code.replace(/\\/g, '/');
|
|
25
25
|
const configRoot = path_1.default.resolve(context.filePath);
|
|
26
26
|
const resolvedPath = path_1.default.resolve(context.filePath, normalizedCode);
|
|
27
|
-
|
|
28
|
-
throw new Error(`Path "${module.code}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
29
|
-
`Move the file inside your config directory.`);
|
|
30
|
-
}
|
|
27
|
+
(0, utils_1.assertInsideConfigRoot)(module.code, resolvedPath, configRoot);
|
|
31
28
|
module.code = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
32
29
|
mappings: context.mappings,
|
|
33
30
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
@@ -26,10 +26,7 @@ function parse(context) {
|
|
|
26
26
|
const normalizedCode = action.code.replace(/\\/g, '/');
|
|
27
27
|
const configRoot = path_1.default.resolve(context.filePath);
|
|
28
28
|
const resolvedPath = path_1.default.resolve(context.filePath, normalizedCode);
|
|
29
|
-
|
|
30
|
-
throw new Error(`Path "${action.code}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
31
|
-
`Move the file inside your config directory.`);
|
|
32
|
-
}
|
|
29
|
+
(0, utils_1.assertInsideConfigRoot)(action.code, resolvedPath, configRoot);
|
|
33
30
|
action.code = (0, tools_1.loadFileAndReplaceKeywords)(resolvedPath, {
|
|
34
31
|
mappings: context.mappings,
|
|
35
32
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
@@ -17,6 +17,7 @@ function attackProtectionFiles(filePath) {
|
|
|
17
17
|
bruteForceProtection: path_1.default.join(directory, 'brute-force-protection.json'),
|
|
18
18
|
captcha: path_1.default.join(directory, 'captcha.json'),
|
|
19
19
|
suspiciousIpThrottling: path_1.default.join(directory, 'suspicious-ip-throttling.json'),
|
|
20
|
+
phoneProviderProtection: path_1.default.join(directory, 'phone-provider-protection.json'),
|
|
20
21
|
};
|
|
21
22
|
}
|
|
22
23
|
function parse(context) {
|
|
@@ -55,6 +56,12 @@ function parse(context) {
|
|
|
55
56
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
56
57
|
});
|
|
57
58
|
}
|
|
59
|
+
if ((0, utils_1.isFile)(files.phoneProviderProtection)) {
|
|
60
|
+
attackProtection.phoneProviderProtection = (0, utils_1.loadJSON)(files.phoneProviderProtection, {
|
|
61
|
+
mappings: context.mappings,
|
|
62
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
58
65
|
return {
|
|
59
66
|
attackProtection,
|
|
60
67
|
};
|
|
@@ -65,7 +72,7 @@ async function dump(context) {
|
|
|
65
72
|
return;
|
|
66
73
|
const files = attackProtectionFiles(context.filePath);
|
|
67
74
|
fs_extra_1.default.ensureDirSync(files.directory);
|
|
68
|
-
const maskedAttackProtection = (0, defaults_1.attackProtectionDefaults)(attackProtection, context.config);
|
|
75
|
+
const maskedAttackProtection = (0, defaults_1.sortAttackProtectionArrays)((0, defaults_1.attackProtectionDefaults)(attackProtection, context.config));
|
|
69
76
|
if (maskedAttackProtection.botDetection) {
|
|
70
77
|
(0, utils_1.dumpJSON)(files.botDetection, maskedAttackProtection.botDetection);
|
|
71
78
|
}
|
|
@@ -81,6 +88,9 @@ async function dump(context) {
|
|
|
81
88
|
if (maskedAttackProtection.suspiciousIpThrottling) {
|
|
82
89
|
(0, utils_1.dumpJSON)(files.suspiciousIpThrottling, maskedAttackProtection.suspiciousIpThrottling);
|
|
83
90
|
}
|
|
91
|
+
if (maskedAttackProtection.phoneProviderProtection) {
|
|
92
|
+
(0, utils_1.dumpJSON)(files.phoneProviderProtection, maskedAttackProtection.phoneProviderProtection);
|
|
93
|
+
}
|
|
84
94
|
}
|
|
85
95
|
const attackProtectionHandler = {
|
|
86
96
|
parse,
|
|
@@ -31,7 +31,10 @@ function parse(context) {
|
|
|
31
31
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
32
32
|
});
|
|
33
33
|
const normalizedPathArray = (0, utils_1.nomalizedYAMLPath)(definition.body);
|
|
34
|
-
|
|
34
|
+
const resolvedBodyFile = path_1.default.resolve(brandingTemplatesFolder, ...normalizedPathArray);
|
|
35
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
36
|
+
(0, utils_1.assertInsideConfigRoot)(definition.body, resolvedBodyFile, configRoot);
|
|
37
|
+
definition.body = (0, tools_1.loadFileAndReplaceKeywords)(resolvedBodyFile, {
|
|
35
38
|
mappings: context.mappings,
|
|
36
39
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
37
40
|
});
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { DirectoryHandler } from '.';
|
|
2
|
+
import { ParsedAsset } from '../../../types';
|
|
3
|
+
type ParsedClientAuthCredentials = ParsedAsset<'clientAuthCredentials', null>;
|
|
4
|
+
declare const clientAuthCredentialsHandler: DirectoryHandler<ParsedClientAuthCredentials>;
|
|
5
|
+
export default clientAuthCredentialsHandler;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
function parse(_context) {
|
|
4
|
+
return { clientAuthCredentials: null };
|
|
5
|
+
}
|
|
6
|
+
async function dump(_context) {
|
|
7
|
+
// Credentials are embedded inside each client's JSON file — nothing to dump separately.
|
|
8
|
+
}
|
|
9
|
+
const clientAuthCredentialsHandler = {
|
|
10
|
+
parse,
|
|
11
|
+
dump,
|
|
12
|
+
};
|
|
13
|
+
exports.default = clientAuthCredentialsHandler;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const path_1 = __importDefault(require("path"));
|
|
7
7
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
8
|
const tools_1 = require("../../../tools");
|
|
9
|
+
const logger_1 = __importDefault(require("../../../logger"));
|
|
9
10
|
const utils_1 = require("../../../utils");
|
|
10
11
|
const client_1 = require("../../../tools/auth0/client");
|
|
11
12
|
const keywordPreservation_1 = require("../../../keywordPreservation");
|
|
@@ -41,34 +42,23 @@ async function dump(context) {
|
|
|
41
42
|
paginate: true,
|
|
42
43
|
include_totals: true,
|
|
43
44
|
});
|
|
44
|
-
//
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
dumpGrant.client_id = (0, utils_1.convertClientIdToName)(dumpGrant.client_id, context.assets.clientsOrig);
|
|
56
|
-
}
|
|
45
|
+
// Convert audience to the API name for readability
|
|
46
|
+
const apiName = (grantAudience) => {
|
|
47
|
+
if (!grantAudience)
|
|
48
|
+
return grantAudience;
|
|
49
|
+
const associatedAPI = allResourceServers.find((resourceServer) => resourceServer.identifier === grantAudience);
|
|
50
|
+
if (associatedAPI === undefined)
|
|
51
|
+
return grantAudience; // Use the audience if the API is not found
|
|
52
|
+
return associatedAPI.name; // Use the name of the API
|
|
53
|
+
};
|
|
54
|
+
// Derive the filename for a grant.
|
|
55
|
+
const nameFor = (grant) => {
|
|
57
56
|
const clientName = (() => {
|
|
58
57
|
const associatedClient = allClients.find((client) => client.client_id === grant.client_id);
|
|
59
58
|
if (associatedClient === undefined)
|
|
60
59
|
return grant.client_id;
|
|
61
60
|
return associatedClient.name;
|
|
62
61
|
})();
|
|
63
|
-
// Convert audience to the API name for readability
|
|
64
|
-
const apiName = (grantAudience) => {
|
|
65
|
-
if (!grantAudience)
|
|
66
|
-
return grantAudience;
|
|
67
|
-
const associatedAPI = allResourceServers.find((resourceServer) => resourceServer.identifier === grantAudience);
|
|
68
|
-
if (associatedAPI === undefined)
|
|
69
|
-
return grantAudience; // Use the audience if the API is not found
|
|
70
|
-
return associatedAPI.name; // Use the name of the API
|
|
71
|
-
};
|
|
72
62
|
// Replace keyword markers if necessary
|
|
73
63
|
const clientNameNonMarker = (0, keywordPreservation_1.doesHaveKeywordMarker)(clientName, context.mappings)
|
|
74
64
|
? (0, tools_1.keywordReplace)(clientName, context.mappings)
|
|
@@ -76,14 +66,78 @@ async function dump(context) {
|
|
|
76
66
|
const apiAudienceNonMarker = (0, keywordPreservation_1.doesHaveKeywordMarker)(grant.audience, context.mappings)
|
|
77
67
|
? (0, tools_1.keywordReplace)(grant.audience, context.mappings)
|
|
78
68
|
: grant.audience;
|
|
79
|
-
// Construct the name using non-marker names
|
|
80
|
-
|
|
69
|
+
// Construct the name using non-marker names. `subject_type` is part of a grant's identity
|
|
70
|
+
// (see `identifiers` in src/tools/auth0/handlers/clientGrants.ts), so it must be included:
|
|
71
|
+
// without it, grants differing only by subject type (e.g. `client` vs `user` on the same
|
|
72
|
+
// client and audience) resolve to the same filename and silently overwrite each other.
|
|
73
|
+
const baseName = `${clientNameNonMarker}-${apiName(apiAudienceNonMarker)}`;
|
|
74
|
+
return (0, utils_1.sanitize)(grant.subject_type ? `${baseName}-${grant.subject_type}` : baseName);
|
|
75
|
+
};
|
|
76
|
+
const excludedClients = allClients.filter((c) => c.name !== undefined && excludedClientsByNames.includes(c.name));
|
|
77
|
+
// Values that can stand for an excluded client in the `client_id` field of a dumped file: the
|
|
78
|
+
// client name when `clientsOrig` was available at dump time (see `convertClientIdToName` below),
|
|
79
|
+
// the raw client_id otherwise. Names come from the exclude list rather than from `allClients` so
|
|
80
|
+
// that excluding a client absent from the tenant still protects its file.
|
|
81
|
+
const excludedClientIdentities = new Set([
|
|
82
|
+
...excludedClientsByNames,
|
|
83
|
+
...excludedClients.map((c) => c.client_id).filter((id) => !!id),
|
|
84
|
+
]);
|
|
85
|
+
// Whether a file this dump did not write must nonetheless survive the cleanup pass. Its name
|
|
86
|
+
// cannot answer that: the name is derived from the client name, the API name, the grant's
|
|
87
|
+
// subject_type and the current naming format, so a file written by an earlier version — or
|
|
88
|
+
// before its API was renamed — no longer matches the name `nameFor` produces today. Read the
|
|
89
|
+
// file instead, because the client identity recorded inside it does not drift.
|
|
90
|
+
const mustPreserve = (file) => {
|
|
91
|
+
if (excludedClientIdentities.size === 0)
|
|
92
|
+
return false;
|
|
93
|
+
let grant;
|
|
94
|
+
try {
|
|
95
|
+
grant = (0, utils_1.loadJSON)(file, {
|
|
96
|
+
mappings: context.mappings,
|
|
97
|
+
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
// Deleting a file it cannot read is not the export's call to make, and one bad file must not
|
|
102
|
+
// fail the whole export. Keep it and let `parse` report the problem on the next import.
|
|
103
|
+
logger_1.default.warn(`Keeping ${file}, it could not be read while cleaning up client grants: ${err}`);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return excludedClientIdentities.has(grant?.client_id);
|
|
107
|
+
};
|
|
108
|
+
// Track files written by this dump; everything else in the folder is a cleanup candidate.
|
|
109
|
+
const expectedFiles = new Set();
|
|
110
|
+
// Filter out grants for excluded clients
|
|
111
|
+
if (excludedClientsByNames.length) {
|
|
112
|
+
const excludedClientIds = new Set(excludedClients.map((c) => c.client_id));
|
|
113
|
+
clientGrants = clientGrants.filter((grant) => !excludedClientIds.has(grant.client_id));
|
|
114
|
+
}
|
|
115
|
+
// Convert client_id to the client name for readability
|
|
116
|
+
clientGrants.forEach((grant) => {
|
|
117
|
+
const dumpGrant = { ...grant };
|
|
118
|
+
if (context.assets.clientsOrig) {
|
|
119
|
+
dumpGrant.client_id = (0, utils_1.convertClientIdToName)(dumpGrant.client_id, context.assets.clientsOrig);
|
|
120
|
+
}
|
|
121
|
+
const name = nameFor(grant);
|
|
81
122
|
// Ensure the name is not empty or invalid
|
|
82
123
|
if (!name || name.trim().length === 0) {
|
|
83
124
|
throw new Error(`Invalid name generated for client grant: ${JSON.stringify(grant)}`);
|
|
84
125
|
}
|
|
85
126
|
const grantFile = path_1.default.join(grantsFolder, `${name}.json`);
|
|
86
127
|
(0, utils_1.dumpJSON)(grantFile, dumpGrant);
|
|
128
|
+
expectedFiles.add(`${name}.json`);
|
|
129
|
+
});
|
|
130
|
+
// Remove files that belong to grants no longer present (and not excluded). Without this, a grant
|
|
131
|
+
// whose filename changes is left behind under its old name and parsed back as a duplicate on the
|
|
132
|
+
// next import, and grants deleted from the tenant are silently recreated.
|
|
133
|
+
//
|
|
134
|
+
// Restricted to the `.json` files `parse` reads: anything else in the folder (a README, notes)
|
|
135
|
+
// can never come back as a grant, so it is not stale state and must not be deleted.
|
|
136
|
+
(0, utils_1.getFiles)(grantsFolder, ['.json'])
|
|
137
|
+
.filter((file) => !expectedFiles.has(path_1.default.basename(file)) && !mustPreserve(file))
|
|
138
|
+
.forEach((file) => {
|
|
139
|
+
logger_1.default.info(`Removing ${file}`);
|
|
140
|
+
fs_extra_1.default.removeSync(file);
|
|
87
141
|
});
|
|
88
142
|
}
|
|
89
143
|
const clientGrantsHandler = {
|
|
@@ -20,9 +20,11 @@ function parse(context) {
|
|
|
20
20
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
21
21
|
});
|
|
22
22
|
if (client.custom_login_page) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
24
|
+
const resolvedLoginPage = path_1.default.resolve(clientsFolder, client.custom_login_page);
|
|
25
|
+
if ((0, utils_1.isFile)(resolvedLoginPage)) {
|
|
26
|
+
(0, utils_1.assertInsideConfigRoot)(client.custom_login_page, resolvedLoginPage, configRoot);
|
|
27
|
+
client.custom_login_page = (0, tools_1.loadFileAndReplaceKeywords)(resolvedLoginPage, {
|
|
26
28
|
mappings: context.mappings,
|
|
27
29
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
28
30
|
});
|
|
@@ -106,6 +108,21 @@ async function dump(context) {
|
|
|
106
108
|
organization_require_behavior: client.organization_require_behavior,
|
|
107
109
|
};
|
|
108
110
|
}
|
|
111
|
+
else {
|
|
112
|
+
// strip id-only credentials and remove client_authentication_methods entirely
|
|
113
|
+
// if no named credentials remain — an absent field is treated as deletion on deploy
|
|
114
|
+
if (client.client_authentication_methods) {
|
|
115
|
+
Object.values(client.client_authentication_methods).forEach((method) => {
|
|
116
|
+
if (method?.credentials) {
|
|
117
|
+
method.credentials = method.credentials.filter((c) => c.name);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
const hasAnyCredentials = Object.values(client.client_authentication_methods).some((method) => method?.credentials?.length > 0);
|
|
121
|
+
if (!hasAnyCredentials) {
|
|
122
|
+
delete client.client_authentication_methods;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
109
126
|
(0, utils_1.dumpJSON)(clientFile, (0, utils_1.clearClientArrays)(client));
|
|
110
127
|
});
|
|
111
128
|
}
|
|
@@ -23,11 +23,13 @@ function parse(context) {
|
|
|
23
23
|
});
|
|
24
24
|
if (connection.strategy === 'email') {
|
|
25
25
|
(0, utils_1.ensureProp)(connection, 'options.email.body');
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
const configRoot = path_1.default.resolve(context.filePath);
|
|
27
|
+
const resolvedHtmlFile = path_1.default.resolve(connectionsFolder, connection.options.email.body);
|
|
28
|
+
if (!(0, utils_1.isFile)(resolvedHtmlFile)) {
|
|
29
|
+
throw new Error(`Passwordless email template purportedly located at ${resolvedHtmlFile} does not exist for connection. Ensure the existence of this file to proceed with deployment.`);
|
|
29
30
|
}
|
|
30
|
-
connection.options.email.body
|
|
31
|
+
(0, utils_1.assertInsideConfigRoot)(connection.options.email.body, resolvedHtmlFile, configRoot);
|
|
32
|
+
connection.options.email.body = (0, tools_1.loadFileAndReplaceKeywords)(resolvedHtmlFile, {
|
|
31
33
|
mappings: context.mappings,
|
|
32
34
|
disableKeywordReplacement: context.disableKeywordReplacement,
|
|
33
35
|
});
|
|
@@ -49,6 +51,11 @@ async function dump(context) {
|
|
|
49
51
|
if (excludedConnections.length) {
|
|
50
52
|
connections = connections.filter((connection) => !excludedConnections.includes(connection.name));
|
|
51
53
|
}
|
|
54
|
+
// Filter to included connections
|
|
55
|
+
const includedConnections = (context.assets.include && context.assets.include.connections) || [];
|
|
56
|
+
if (includedConnections.length) {
|
|
57
|
+
connections = connections.filter((connection) => includedConnections.includes(connection.name));
|
|
58
|
+
}
|
|
52
59
|
const connectionsFolder = path_1.default.join(context.filePath, tools_1.constants.CONNECTIONS_DIRECTORY);
|
|
53
60
|
fs_extra_1.default.ensureDirSync(connectionsFolder);
|
|
54
61
|
// Track files that should remain after dump (written + excluded).
|
|
@@ -93,11 +100,18 @@ async function dump(context) {
|
|
|
93
100
|
if (dumpedConnection.strategy === 'email')
|
|
94
101
|
expectedFiles.add(`${connectionName}.html`);
|
|
95
102
|
});
|
|
103
|
+
// With an include list configured, connections outside it are unmanaged, so limit pruning
|
|
104
|
+
// to the listed names rather than every file in the folder.
|
|
105
|
+
const prunableFiles = includedConnections.length
|
|
106
|
+
? new Set(includedConnections.flatMap((name) => [`${(0, utils_1.sanitize)(name)}.json`, `${(0, utils_1.sanitize)(name)}.html`]))
|
|
107
|
+
: null;
|
|
96
108
|
// Remove files that belong to connections no longer present (and not excluded)
|
|
97
109
|
if (fs_extra_1.default.existsSync(connectionsFolder)) {
|
|
98
110
|
for (const existing of fs_extra_1.default.readdirSync(connectionsFolder)) {
|
|
99
111
|
const fullPath = path_1.default.join(connectionsFolder, existing);
|
|
100
|
-
if (fs_extra_1.default.statSync(fullPath).isFile() &&
|
|
112
|
+
if (fs_extra_1.default.statSync(fullPath).isFile() &&
|
|
113
|
+
!expectedFiles.has(existing) &&
|
|
114
|
+
(prunableFiles === null || prunableFiles.has(existing))) {
|
|
101
115
|
fs_extra_1.default.removeSync(fullPath);
|
|
102
116
|
}
|
|
103
117
|
}
|
|
@@ -42,10 +42,7 @@ function getDatabase(folder, configRoot, mappingOpts) {
|
|
|
42
42
|
else {
|
|
43
43
|
const resolvedBase = path_1.default.resolve(configRoot);
|
|
44
44
|
const toLoad = path_1.default.resolve(folder, script.replace(/\\/g, '/'));
|
|
45
|
-
|
|
46
|
-
throw new Error(`Path "${script}" resolves to "${toLoad}" which is outside the config directory "${resolvedBase}". ` +
|
|
47
|
-
`Move the file inside your config directory.`);
|
|
48
|
-
}
|
|
45
|
+
(0, utils_1.assertInsideConfigRoot)(script, toLoad, resolvedBase);
|
|
49
46
|
database.options.customScripts[name] = (0, tools_1.loadFileAndReplaceKeywords)(toLoad, mappingOpts);
|
|
50
47
|
}
|
|
51
48
|
});
|