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/lib/tools/utils.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export declare function areArraysEquals(x: any[], y: any[]): boolean;
|
|
|
27
27
|
export declare const obfuscateSensitiveValues: (data: Asset | Asset[] | null, sensitiveFieldsToObfuscate: string[]) => Asset | Asset[] | null;
|
|
28
28
|
export declare const validateNoUnresolvedPlaceholders: (data: Asset | null, resourceType: string, resourceName: string) => Asset | null;
|
|
29
29
|
export declare const stripObfuscatedFieldsFromPayload: (data: Asset | Asset[] | null, obfuscatedFields: string[]) => Asset | Asset[] | null;
|
|
30
|
+
export declare const stripUnresolvedPlaceholders: (data: Asset | null, resourceType: string, resourceName: string) => Asset | null;
|
|
30
31
|
export declare const detectInsufficientScopeError: <T>(fn: Function) => Promise<{
|
|
31
32
|
hadSufficientScopes: true;
|
|
32
33
|
data: T;
|
|
@@ -41,6 +42,8 @@ export declare const isDeprecatedError: (err: {
|
|
|
41
42
|
message: string;
|
|
42
43
|
statusCode: number;
|
|
43
44
|
}) => boolean;
|
|
45
|
+
export declare const isFeatureUnavailableError: (err: any) => boolean;
|
|
46
|
+
export declare const isInsufficientEntitlementError: (err: unknown) => boolean;
|
|
44
47
|
export declare const isForbiddenFeatureError: (err: any, type: any) => boolean;
|
|
45
48
|
export declare function maskSecretAtPath({ resourceTypeName, maskedKeyName, maskOnObj, keyJsonPath, }: {
|
|
46
49
|
resourceTypeName: string;
|
package/lib/tools/utils.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.shouldExcludeThirdPartyClients = exports.isForbiddenFeatureError = exports.isDeprecatedError = exports.detectInsufficientScopeError = exports.stripObfuscatedFieldsFromPayload = exports.validateNoUnresolvedPlaceholders = exports.obfuscateSensitiveValues = exports.keywordReplaceStringRegExp = exports.keywordReplaceArrayRegExp = void 0;
|
|
39
|
+
exports.shouldExcludeThirdPartyClients = exports.isForbiddenFeatureError = exports.isInsufficientEntitlementError = exports.isFeatureUnavailableError = exports.isDeprecatedError = exports.detectInsufficientScopeError = exports.stripUnresolvedPlaceholders = exports.stripObfuscatedFieldsFromPayload = exports.validateNoUnresolvedPlaceholders = exports.obfuscateSensitiveValues = exports.keywordReplaceStringRegExp = exports.keywordReplaceArrayRegExp = void 0;
|
|
40
40
|
exports.keywordArrayReplace = keywordArrayReplace;
|
|
41
41
|
exports.keywordStringReplace = keywordStringReplace;
|
|
42
42
|
exports.keywordReplace = keywordReplace;
|
|
@@ -309,6 +309,24 @@ const stripObfuscatedFieldsFromPayload = (data, obfuscatedFields) => {
|
|
|
309
309
|
return newAsset;
|
|
310
310
|
};
|
|
311
311
|
exports.stripObfuscatedFieldsFromPayload = stripObfuscatedFieldsFromPayload;
|
|
312
|
+
// Strips unresolved ##...## / @@...@@ placeholders before sending to Auth0.
|
|
313
|
+
// Keyword replacement runs first; any remaining placeholder means no mapping was
|
|
314
|
+
// provided. The field is dropped so Auth0 preserves its existing value.
|
|
315
|
+
const stripUnresolvedPlaceholders = (data, resourceType, resourceName) => {
|
|
316
|
+
if (data === null)
|
|
317
|
+
return data;
|
|
318
|
+
const unresolved = collectUnresolvedPlaceholders(data);
|
|
319
|
+
if (unresolved.length === 0)
|
|
320
|
+
return data;
|
|
321
|
+
const newAsset = { ...data };
|
|
322
|
+
unresolved.forEach(({ path, value }) => {
|
|
323
|
+
logger_1.default.warn(`Stripping unresolved placeholder for ${resourceType} "${resourceName}" field "${path}": ${value}. ` +
|
|
324
|
+
`To use this value, define ${value.replace(/^##|##$|^@@|@@$/g, '')} in AUTH0_KEYWORD_REPLACE_MAPPINGS. The existing value on Auth0 will be preserved.`);
|
|
325
|
+
dot_prop_1.default.delete(newAsset, path);
|
|
326
|
+
});
|
|
327
|
+
return newAsset;
|
|
328
|
+
};
|
|
329
|
+
exports.stripUnresolvedPlaceholders = stripUnresolvedPlaceholders;
|
|
312
330
|
const detectInsufficientScopeError = async (fn) => {
|
|
313
331
|
try {
|
|
314
332
|
const data = await fn();
|
|
@@ -340,9 +358,30 @@ const isDeprecatedError = (err) => {
|
|
|
340
358
|
return !!(err.statusCode === 403 || err.message?.includes('deprecated feature'));
|
|
341
359
|
};
|
|
342
360
|
exports.isDeprecatedError = isDeprecatedError;
|
|
361
|
+
const isFeatureUnavailableError = (err) => {
|
|
362
|
+
// A 404 indicates an older Management API version where the endpoint is not available.
|
|
363
|
+
// 403s (feature explicitly disabled) are handled by isForbiddenFeatureError.
|
|
364
|
+
return err.statusCode === 404;
|
|
365
|
+
};
|
|
366
|
+
exports.isFeatureUnavailableError = isFeatureUnavailableError;
|
|
367
|
+
const isInsufficientEntitlementError = (err) => {
|
|
368
|
+
if (!err || typeof err !== 'object')
|
|
369
|
+
return false;
|
|
370
|
+
const e = err;
|
|
371
|
+
if (e.statusCode !== 403)
|
|
372
|
+
return false;
|
|
373
|
+
const body = e.body;
|
|
374
|
+
return body?.errorCode === 'insufficient_entitlement';
|
|
375
|
+
};
|
|
376
|
+
exports.isInsufficientEntitlementError = isInsufficientEntitlementError;
|
|
343
377
|
const isForbiddenFeatureError = (err, type) => {
|
|
344
378
|
if (err.statusCode === 403) {
|
|
345
|
-
|
|
379
|
+
// The SDK error's top-level `message` is the full serialized response body; the clean,
|
|
380
|
+
// human-readable message and errorCode live on the response body itself.
|
|
381
|
+
const body = err.originalError?.response?.body ?? {};
|
|
382
|
+
const message = body.message ?? err.message;
|
|
383
|
+
const errorCode = body.errorCode ?? err.errorCode ?? '';
|
|
384
|
+
logger_1.default.warn(`${message}${errorCode ? ` (${errorCode})` : ''} - Skipping ${type}`);
|
|
346
385
|
return true;
|
|
347
386
|
}
|
|
348
387
|
return false;
|
package/lib/types.d.ts
CHANGED
|
@@ -16,11 +16,13 @@ import { SsProfileWithCustomText } from './tools/auth0/handlers/selfServiceProfi
|
|
|
16
16
|
import { PhoneProvider } from './tools/auth0/handlers/phoneProvider';
|
|
17
17
|
import { PhoneTemplate } from './tools/auth0/handlers/phoneTemplates';
|
|
18
18
|
import { NetworkACL } from './tools/auth0/handlers/networkACLs';
|
|
19
|
+
import { NetworkAclKey } from './tools/auth0/handlers/networkACLKeys';
|
|
19
20
|
import { UserAttributeProfile } from './tools/auth0/handlers/userAttributeProfiles';
|
|
20
21
|
import { AttackProtection } from './tools/auth0/handlers/attackProtection';
|
|
21
22
|
import { TokenExchangeProfile } from './tools/auth0/handlers/tokenExchangeProfiles';
|
|
22
23
|
import { RiskAssessment } from './tools/auth0/handlers/riskAssessment';
|
|
23
24
|
import { SupplementalSignals } from './tools/auth0/handlers/supplementalSignals';
|
|
25
|
+
import { RateLimitPolicy } from './tools/auth0/handlers/rateLimitPolicies';
|
|
24
26
|
import { EventStream } from './tools/auth0/handlers/eventStreams';
|
|
25
27
|
type SharedPaginationParams = {
|
|
26
28
|
checkpoint?: boolean;
|
|
@@ -30,6 +32,7 @@ type SharedPaginationParams = {
|
|
|
30
32
|
include_totals?: boolean;
|
|
31
33
|
id?: string;
|
|
32
34
|
strategy?: Management.ConnectionStrategyEnum[];
|
|
35
|
+
type?: Management.RoleTypeEnum;
|
|
33
36
|
};
|
|
34
37
|
export type CheckpointPaginationParams = SharedPaginationParams & {
|
|
35
38
|
from?: string;
|
|
@@ -74,6 +77,7 @@ export type Config = {
|
|
|
74
77
|
AUTH0_EXPORT_IDENTIFIERS?: boolean;
|
|
75
78
|
AUTH0_EXPORT_ORDERED?: boolean;
|
|
76
79
|
AUTH0_EXPORT_SECRETS?: boolean;
|
|
80
|
+
AUTH0_EXPORT_CUSTOM_TEXT_LANGUAGES?: string[];
|
|
77
81
|
AUTH0_CONNECTIONS_DIRECTORY?: string;
|
|
78
82
|
AUTH0_DRY_RUN?: boolean | 'preview';
|
|
79
83
|
AUTH0_DRY_RUN_INTERACTIVE?: boolean;
|
|
@@ -127,6 +131,9 @@ export type Assets = Partial<{
|
|
|
127
131
|
message_types: Asset[];
|
|
128
132
|
} | null;
|
|
129
133
|
guardianPhoneFactorSelectedProvider: Asset | null;
|
|
134
|
+
guardianPhoneFactorSettings: Asset | null;
|
|
135
|
+
guardianEmailFactorSettings: Asset | null;
|
|
136
|
+
guardianSettings: Asset | null;
|
|
130
137
|
guardianPolicies: {
|
|
131
138
|
policies: string[];
|
|
132
139
|
} | null;
|
|
@@ -154,11 +161,13 @@ export type Assets = Partial<{
|
|
|
154
161
|
flows: Flow[] | null;
|
|
155
162
|
flowVaultConnections: FlowVaultConnection[] | null;
|
|
156
163
|
selfServiceProfiles: SsProfileWithCustomText[] | null;
|
|
164
|
+
networkACLKeys: NetworkAclKey[] | null;
|
|
157
165
|
networkACLs: NetworkACL[] | null;
|
|
158
166
|
userAttributeProfiles: UserAttributeProfile[] | null;
|
|
159
167
|
userAttributeProfilesWithId: UserAttributeProfile[] | null;
|
|
160
168
|
connectionProfiles: Asset[] | null;
|
|
161
169
|
tokenExchangeProfiles: TokenExchangeProfile[] | null;
|
|
170
|
+
rateLimitPolicies: RateLimitPolicy[] | null;
|
|
162
171
|
eventStreams: EventStream[] | null;
|
|
163
172
|
}>;
|
|
164
173
|
export type CalculatedChanges = {
|
|
@@ -180,7 +189,7 @@ export type DetailedDryRunChanges = {
|
|
|
180
189
|
changes: DetailedDryRunChange[];
|
|
181
190
|
};
|
|
182
191
|
};
|
|
183
|
-
export type AssetTypes = 'rules' | 'rulesConfigs' | 'hooks' | 'pages' | 'databases' | 'clientGrants' | 'resourceServers' | 'clients' | 'connections' | 'tenant' | 'emailProvider' | 'emailTemplates' | 'guardianFactors' | 'guardianFactorProviders' | 'guardianFactorTemplates' | 'guardianPhoneFactorMessageTypes' | 'guardianPhoneFactorSelectedProvider' | 'guardianPolicies' | 'roles' | 'actions' | 'actionModules' | 'organizations' | 'triggers' | 'attackProtection' | 'riskAssessment' | 'branding' | 'phoneProviders' | 'phoneTemplates' | 'logStreams' | 'prompts' | 'customDomains' | 'themes' | 'forms' | 'flows' | 'flowVaultConnections' | 'selfServiceProfiles' | 'networkACLs' | 'userAttributeProfiles' | 'connectionProfiles' | 'tokenExchangeProfiles' | 'supplementalSignals' | 'eventStreams';
|
|
192
|
+
export type AssetTypes = 'rules' | 'rulesConfigs' | 'hooks' | 'pages' | 'databases' | 'clientGrants' | 'resourceServers' | 'clients' | 'connections' | 'tenant' | 'emailProvider' | 'emailTemplates' | 'guardianFactors' | 'guardianFactorProviders' | 'guardianFactorTemplates' | 'guardianPhoneFactorMessageTypes' | 'guardianPhoneFactorSelectedProvider' | 'guardianPhoneFactorSettings' | 'guardianEmailFactorSettings' | 'guardianSettings' | 'guardianPolicies' | 'roles' | 'actions' | 'actionModules' | 'organizations' | 'triggers' | 'attackProtection' | 'riskAssessment' | 'branding' | 'phoneProviders' | 'phoneTemplates' | 'logStreams' | 'prompts' | 'customDomains' | 'themes' | 'forms' | 'flows' | 'flowVaultConnections' | 'selfServiceProfiles' | 'networkACLKeys' | 'networkACLs' | 'userAttributeProfiles' | 'connectionProfiles' | 'tokenExchangeProfiles' | 'supplementalSignals' | 'rateLimitPolicies' | 'eventStreams' | 'clientAuthCredentials';
|
|
184
193
|
export type KeywordMappings = {
|
|
185
194
|
[key: string]: (string | number)[] | string | number;
|
|
186
195
|
};
|
package/lib/utils.d.ts
CHANGED
|
@@ -52,4 +52,10 @@ export declare const getFormattedOptions: (connection: any, clients: any) => {
|
|
|
52
52
|
} | {
|
|
53
53
|
options?: undefined;
|
|
54
54
|
};
|
|
55
|
+
/**
|
|
56
|
+
* Asserts that `resolvedPath` is inside `configRoot`.
|
|
57
|
+
* Throws a hard error on path traversal attempts so there is a single,
|
|
58
|
+
* canonical enforcement point across all handlers.
|
|
59
|
+
*/
|
|
60
|
+
export declare function assertInsideConfigRoot(input: string, resolvedPath: string, configRoot: string): void;
|
|
55
61
|
export {};
|
package/lib/utils.js
CHANGED
|
@@ -23,6 +23,7 @@ exports.convertClientIdToName = convertClientIdToName;
|
|
|
23
23
|
exports.hasKeywordMarkers = hasKeywordMarkers;
|
|
24
24
|
exports.mapClientID2NameSorted = mapClientID2NameSorted;
|
|
25
25
|
exports.nomalizedYAMLPath = nomalizedYAMLPath;
|
|
26
|
+
exports.assertInsideConfigRoot = assertInsideConfigRoot;
|
|
26
27
|
const path_1 = __importDefault(require("path"));
|
|
27
28
|
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
28
29
|
const nconf_1 = __importDefault(require("nconf"));
|
|
@@ -301,12 +302,15 @@ exports.decodeBase64ToCertString = decodeBase64ToCertString;
|
|
|
301
302
|
// Format connection options by converting client IDs to client names for SAML connections
|
|
302
303
|
const getFormattedOptions = (connection, clients) => {
|
|
303
304
|
try {
|
|
305
|
+
const { idpinitiated } = connection.options;
|
|
304
306
|
return {
|
|
305
307
|
options: {
|
|
306
308
|
...connection.options,
|
|
307
309
|
idpinitiated: {
|
|
308
|
-
...
|
|
309
|
-
|
|
310
|
+
...idpinitiated,
|
|
311
|
+
...(idpinitiated.client_id && {
|
|
312
|
+
client_id: convertClientIdToName(idpinitiated.client_id, clients),
|
|
313
|
+
}),
|
|
310
314
|
},
|
|
311
315
|
},
|
|
312
316
|
};
|
|
@@ -316,3 +320,14 @@ const getFormattedOptions = (connection, clients) => {
|
|
|
316
320
|
}
|
|
317
321
|
};
|
|
318
322
|
exports.getFormattedOptions = getFormattedOptions;
|
|
323
|
+
/**
|
|
324
|
+
* Asserts that `resolvedPath` is inside `configRoot`.
|
|
325
|
+
* Throws a hard error on path traversal attempts so there is a single,
|
|
326
|
+
* canonical enforcement point across all handlers.
|
|
327
|
+
*/
|
|
328
|
+
function assertInsideConfigRoot(input, resolvedPath, configRoot) {
|
|
329
|
+
if (!resolvedPath.startsWith(configRoot + path_1.default.sep)) {
|
|
330
|
+
throw new Error(`Path traversal blocked: "${input}" resolves to "${resolvedPath}" which is outside the config directory "${configRoot}". ` +
|
|
331
|
+
`Move the file inside your config directory.`);
|
|
332
|
+
}
|
|
333
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth0-deploy-cli",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"description": "A command line tool for deploying updates to your Auth0 tenant",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"@clack/prompts": "1.4.0",
|
|
37
37
|
"ajv": "^6.12.6",
|
|
38
|
-
"auth0": "^
|
|
38
|
+
"auth0": "^7.3.0",
|
|
39
39
|
"chalk": "5.6.2",
|
|
40
40
|
"dot-prop": "^5.3.0",
|
|
41
41
|
"fs-extra": "^10.1.0",
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Code Style Reference — auth0-deploy-cli
|
|
2
|
+
|
|
3
|
+
## Naming Conventions
|
|
4
|
+
|
|
5
|
+
| Element | Convention | Example |
|
|
6
|
+
| -------------------- | ---------------- | ----------------------------------- |
|
|
7
|
+
| Source files | camelCase | `clients.ts`, `attackProtection.ts` |
|
|
8
|
+
| Classes | PascalCase | `ClientsHandler`, `YAMLContext` |
|
|
9
|
+
| Methods / variables | camelCase | `processChanges`, `calcChanges` |
|
|
10
|
+
| Constants / env vars | UPPER_SNAKE_CASE | `AUTH0_ALLOW_DELETE` |
|
|
11
|
+
| Test files | `<name>.test.js` | `clients.test.js` |
|
|
12
|
+
|
|
13
|
+
## Handler Implementation Pattern
|
|
14
|
+
|
|
15
|
+
Every resource handler in `src/tools/auth0/handlers/` must implement four methods:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
class ResourceHandler extends DefaultHandler {
|
|
19
|
+
// Fetch current state from Auth0 API
|
|
20
|
+
async getType(): Promise<Asset[]> { ... }
|
|
21
|
+
|
|
22
|
+
// Determine create / update / delete operations
|
|
23
|
+
async calcChanges(assets: Assets): Promise<Changes> { ... }
|
|
24
|
+
|
|
25
|
+
// Execute CRUD operations — must be a no-op in dry run
|
|
26
|
+
async processChanges(assets: Assets, changes: Changes): Promise<void> { ... }
|
|
27
|
+
|
|
28
|
+
// Validate assets before processing; base class provides a default
|
|
29
|
+
async validate(assets: Assets): Promise<void> { ... }
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
> `dump()` / export formatting is handled by `YAMLContext` and `DirectoryContext`, not by handlers.
|
|
34
|
+
|
|
35
|
+
Register the handler in `src/tools/constants.ts`. Handler schemas are defined inline in the handler file and aggregated by `src/tools/auth0/schema.ts`.
|
|
36
|
+
|
|
37
|
+
## Identifiers
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
// ✅ Stable name-based identifier (passed as constructor option to super())
|
|
41
|
+
super({ ...options, identifiers: ['name'] });
|
|
42
|
+
|
|
43
|
+
// ✅ ID alongside name (when the API uses both for matching)
|
|
44
|
+
super({ ...options, identifiers: ['client_id', 'name'] });
|
|
45
|
+
|
|
46
|
+
// 🚫 Never — sole auto-generated UUID breaks cross-tenant portability
|
|
47
|
+
super({ ...options, identifiers: ['id'] });
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Keyword Replacement
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
# @@KEY@@ — JSON-stringified (arrays, objects, booleans, numbers)
|
|
54
|
+
allowed_clients: @@ALLOWED_CLIENTS@@
|
|
55
|
+
|
|
56
|
+
# ##KEY## — literal string substitution (plain strings only)
|
|
57
|
+
name: "##APP_NAME##"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Both patterns must be preserved in every new field that flows through export/import. Mixing them up causes silent corruption — a `##KEY##` around a JSON array produces invalid YAML.
|
|
61
|
+
|
|
62
|
+
## Good vs Bad Handler Example
|
|
63
|
+
|
|
64
|
+
**✅ Good — follows the pattern, respects dry-run and ALLOW_DELETE:**
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
async processChanges(assets: Assets, changes: Changes): Promise<void> {
|
|
68
|
+
if (this.config('AUTH0_ALLOW_DELETE')) {
|
|
69
|
+
await Promise.all(changes.delete.map((r) => this.client.resource.delete({ id: r.id })));
|
|
70
|
+
}
|
|
71
|
+
await Promise.all(changes.create.map((r) => this.client.resource.create(r)));
|
|
72
|
+
await Promise.all(changes.update.map((r) => this.client.resource.update({ id: r.id }, r)));
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**❌ Bad — logs a secret, missing ALLOW_DELETE guard:**
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
async processChanges(assets: Assets, changes: Changes): Promise<void> {
|
|
80
|
+
console.log('client_secret:', assets.clients[0].client_secret); // never log secrets
|
|
81
|
+
// missing AUTH0_ALLOW_DELETE check — deletes happen unconditionally
|
|
82
|
+
await Promise.all(changes.delete.map((r) => this.client.clients.delete({ client_id: r.client_id })));
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## @order() Decorator
|
|
87
|
+
|
|
88
|
+
Handlers run in dependency order. Set `@order(N)` so that handlers whose resources are depended upon by others run first:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
@order(30) // runs after Connections (20) and before Rules (40)
|
|
92
|
+
export default class ClientsHandler extends DefaultHandler { ... }
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Wrong order causes 404s or constraint errors mid-deploy.
|
|
96
|
+
|
|
97
|
+
## Adding a New Resource Handler
|
|
98
|
+
|
|
99
|
+
1. Create handler in `src/tools/auth0/handlers/<resource>.ts`
|
|
100
|
+
2. Extend `DefaultHandler` and implement all four methods (`getType`, `calcChanges`, `processChanges`, `validate`)
|
|
101
|
+
3. Add the resource type constant to `src/tools/constants.ts`
|
|
102
|
+
4. Define the JSON schema inline in the handler; it is aggregated by `src/tools/auth0/schema.ts`
|
|
103
|
+
5. Write unit tests in `test/tools/auth0/handlers/<resource>.test.js`
|
|
104
|
+
6. Add E2E test coverage if applicable (or document why it's not possible)
|
|
105
|
+
7. Update `docs/resource-specific-documentation.md` and `examples/` to demonstrate the new resource
|
|
106
|
+
|
|
107
|
+
## Context Parser Details
|
|
108
|
+
|
|
109
|
+
- **DirectoryContext** (`src/context/directory/`): loads each resource type from separate nested JSON files
|
|
110
|
+
- **YAMLContext** (`src/context/yaml/`): parses a single `tenant.yaml` file with all resources inline
|
|
111
|
+
- Both support `@@KEY@@` and `##KEY##` keyword substitution
|
|
112
|
+
- Context tests use temporary directories with fixtures in `test/context/{directory,yaml}/`
|
|
113
|
+
|
|
114
|
+
## Configuration and Error Handling
|
|
115
|
+
|
|
116
|
+
**Config priority order:** CLI args → env vars → config files → defaults
|
|
117
|
+
|
|
118
|
+
**Error handling conventions:**
|
|
119
|
+
|
|
120
|
+
- Use `ValidationError` from `src/tools/validationError.ts` for all validation failures
|
|
121
|
+
- Validate early, before making API calls
|
|
122
|
+
- Preserve context when bubbling errors up the call stack
|
|
123
|
+
- Provide clear, actionable error messages
|
|
124
|
+
|
|
125
|
+
## Architecture: Request Flow
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
src/index.ts CLI entry — routes export / import commands
|
|
129
|
+
src/commands/ import.ts, export.ts — load config and context
|
|
130
|
+
src/tools/deploy.ts Orchestrate deployment across all handlers
|
|
131
|
+
src/tools/auth0/handlers/* Resource-specific getType / calcChanges / processChanges
|
|
132
|
+
src/tools/auth0/client.ts Management API calls via the auth0 npm SDK
|
|
133
|
+
```
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Commands Reference — auth0-deploy-cli
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Install dependencies (first-time setup)
|
|
7
|
+
npm install
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Build
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# Compile TypeScript → lib/ (cleans lib/ first via rimraf)
|
|
14
|
+
npm run build
|
|
15
|
+
|
|
16
|
+
# Compile and watch (development)
|
|
17
|
+
npm run dev
|
|
18
|
+
|
|
19
|
+
# Type check only (no output)
|
|
20
|
+
npx tsc --noEmit
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Unit Tests
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# Run all unit tests (safe — no credentials required)
|
|
27
|
+
npm test
|
|
28
|
+
|
|
29
|
+
# Run a specific test file
|
|
30
|
+
npx ts-mocha -p tsconfig.json test/tools/auth0/handlers/clients.test.js
|
|
31
|
+
|
|
32
|
+
# Run tests matching a pattern
|
|
33
|
+
npm test -- --grep "should create client"
|
|
34
|
+
|
|
35
|
+
# Run with coverage
|
|
36
|
+
npm run test:coverage
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Lint
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# ESLint + kacl changelog lint
|
|
43
|
+
npm run lint
|
|
44
|
+
|
|
45
|
+
# Auto-fix ESLint issues
|
|
46
|
+
npm run lint:fix
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## E2E Tests
|
|
50
|
+
|
|
51
|
+
> ⚠️ These tests hit a real Auth0 tenant. Ask before running — see [Boundaries](../CLAUDE.md#boundaries).
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# E2E as Node module (uses HTTP recordings)
|
|
55
|
+
AUTH0_HTTP_RECORDINGS=lockdown npm run test:e2e:node-module
|
|
56
|
+
|
|
57
|
+
# E2E as CLI (requires real tenant credentials)
|
|
58
|
+
npm run test:e2e:cli
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Required environment variables for E2E:
|
|
62
|
+
|
|
63
|
+
- `AUTH0_DOMAIN`
|
|
64
|
+
- `AUTH0_CLIENT_ID`
|
|
65
|
+
- `AUTH0_CLIENT_SECRET`
|
|
66
|
+
- `AUTH0_HTTP_RECORDINGS=lockdown` (for node-module recording replay)
|
|
67
|
+
|
|
68
|
+
## Running the CLI Directly
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# Always build first
|
|
72
|
+
npm run build
|
|
73
|
+
|
|
74
|
+
# Export tenant configuration
|
|
75
|
+
node lib/index.js export -c config.json -f directory -o ./local/
|
|
76
|
+
node lib/index.js export -c config.json -f yaml -o ./local-yaml/
|
|
77
|
+
|
|
78
|
+
# Import (deploy) configuration
|
|
79
|
+
node lib/index.js import -c config.json -i ./local/tenant.json
|
|
80
|
+
node lib/index.js import -c config.json -i ./local-yaml/tenant.yaml
|
|
81
|
+
|
|
82
|
+
# Enable debug logging
|
|
83
|
+
AUTH0_DEBUG=true node lib/index.js import -c config.json -i ./local/tenant.json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Environment Variables Reference
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
AUTH0_DOMAIN # Tenant domain (e.g. my-tenant.auth0.com)
|
|
90
|
+
AUTH0_CLIENT_ID # Client ID for M2M application
|
|
91
|
+
AUTH0_CLIENT_SECRET # Client secret
|
|
92
|
+
AUTH0_ALLOW_DELETE=true # Enable delete operations (default: false)
|
|
93
|
+
AUTH0_KEYWORD_REPLACE_MAPPINGS # JSON object for @@KEY@@/##KEY## substitution
|
|
94
|
+
AUTH0_EXCLUDED # JSON array of resource types to skip
|
|
95
|
+
AUTH0_EXCLUDED_<TYPE> # Exclude specific resources by name, e.g. AUTH0_EXCLUDED_CLIENTS
|
|
96
|
+
AUTH0_DEBUG=true # Enable verbose debug logging
|
|
97
|
+
AUTH0_HTTP_RECORDINGS=lockdown # Replay HTTP recordings in E2E node-module tests
|
|
98
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Docs Update Rules — auth0-deploy-cli
|
|
2
|
+
|
|
3
|
+
## Tracked Docs Inventory
|
|
4
|
+
|
|
5
|
+
| Doc | Status | Covers |
|
|
6
|
+
| --------------------- | ---------- | --------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `README.md` | present | Installation, quick-start, prerequisites, CLI usage, configuration options, environment variables, contributing |
|
|
8
|
+
| `EXAMPLES.md` | ❌ missing | Create when adding a new integration pattern or command example |
|
|
9
|
+
| `examples/yaml/` | present | Sample YAML-format tenant configuration |
|
|
10
|
+
| `examples/directory/` | present | Sample directory-format tenant configuration |
|
|
11
|
+
|
|
12
|
+
## Code-to-Docs Mapping (CLI tool)
|
|
13
|
+
|
|
14
|
+
This is a CLI tool. The public surface is **commands, subcommands, and flags** — not exported functions.
|
|
15
|
+
|
|
16
|
+
| When this changes | Update these docs |
|
|
17
|
+
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| A command or subcommand added, removed, or renamed | `README.md` (command reference / usage) |
|
|
19
|
+
| A flag or argument added, removed, renamed, or its default changed | `README.md` (command reference), `examples/` (affected invocations) |
|
|
20
|
+
| Output format or exit-code behavior changed | `README.md` (usage), `examples/` |
|
|
21
|
+
| A new resource type added or removed | `README.md` (supported resources section), `examples/yaml/` and `examples/directory/` (add sample config) |
|
|
22
|
+
| A new config field or env var added | `README.md` (configuration / environment variables section) |
|
|
23
|
+
| A new keyword replacement pattern or behavior | `README.md` (keyword replacement section) |
|
|
24
|
+
|
|
25
|
+
> When you touch code that maps to a doc above, update that doc **in the same PR** — do not defer.
|
|
26
|
+
|
|
27
|
+
> `CHANGELOG.md` is maintained as part of the release flow — do not add changelog entries during feature development; they are cut at release time via the release process.
|
|
28
|
+
|
|
29
|
+
## Additional Reference Docs
|
|
30
|
+
|
|
31
|
+
- `docs/` — extended documentation including resource-specific docs; update `docs/resource-specific-documentation.md` when adding a new handler
|
|
32
|
+
- `docs/v8_MIGRATION_GUIDE.md` — v8 migration context; when making a breaking change, consult this pattern to author a versioned migration guide (filename inferred from the target major at the time of the change)
|
|
33
|
+
- `CONTRIBUTING.md` — contribution guidelines
|
|
34
|
+
- `.github/ISSUE_TEMPLATE/` — issue templates for bug reports and feature requests
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Git Workflow Reference — auth0-deploy-cli
|
|
2
|
+
|
|
3
|
+
## Branch Naming
|
|
4
|
+
|
|
5
|
+
`DXCDT-XXXX` (Jira ticket number) for feature and fix branches.
|
|
6
|
+
|
|
7
|
+
Examples: `DXCDT-2268`, `SDK-10176`
|
|
8
|
+
|
|
9
|
+
## Commit Messages
|
|
10
|
+
|
|
11
|
+
Conventional Commits format:
|
|
12
|
+
|
|
13
|
+
- `feat: add support for new resource type`
|
|
14
|
+
- `fix: resolve table formatting issue`
|
|
15
|
+
- `docs: update handler implementation guide`
|
|
16
|
+
- `test: add coverage for keyword replacement`
|
|
17
|
+
- `refactor: simplify change calculation logic`
|
|
18
|
+
|
|
19
|
+
## Pull Request Conventions
|
|
20
|
+
|
|
21
|
+
PR template is at `.github/PULL_REQUEST_TEMPLATE.md`. Sections:
|
|
22
|
+
|
|
23
|
+
- **Changes** — what was changed and why
|
|
24
|
+
- **References** — linked Jira ticket or GitHub issue
|
|
25
|
+
- **Testing** — what was tested, and any scenarios that could not be tested (EA / entitlement-gated)
|
|
26
|
+
- **Checklist** — tests pass, lint passes, docs updated, backward compatibility checked
|
|
27
|
+
|
|
28
|
+
## PR Review Checklist
|
|
29
|
+
|
|
30
|
+
> This is a living document. Add a new section, checklist item, or Lesson Learned whenever a real review surfaces a pattern worth capturing.
|
|
31
|
+
|
|
32
|
+
Work through every section in order. Do not skip sections because they seem irrelevant. Give a structured summary after all areas: issues found (grouped by severity), items that look good, and questions for the author.
|
|
33
|
+
|
|
34
|
+
### 1. Context & Purpose
|
|
35
|
+
|
|
36
|
+
- Does the PR description explain **why** this change is needed, not just what it does?
|
|
37
|
+
- Is there a linked issue? Does the PR actually close it?
|
|
38
|
+
- Is the scope reasonable — one thing, not many things?
|
|
39
|
+
|
|
40
|
+
### 2. Correctness
|
|
41
|
+
|
|
42
|
+
- Does the logic do what the description claims?
|
|
43
|
+
- Are early exits and guard clauses correct — not too broad, not too narrow?
|
|
44
|
+
- Are error paths handled? Does a failure in one step leave state inconsistent?
|
|
45
|
+
- Are there silent failures (swallowed errors, empty catch blocks)?
|
|
46
|
+
- Does the ordering of operations matter? Is it correct? (e.g. create before delete)
|
|
47
|
+
|
|
48
|
+
### 3. Edge Cases
|
|
49
|
+
|
|
50
|
+
- What happens with empty input / null / undefined?
|
|
51
|
+
- What happens when the resource already exists (idempotency)?
|
|
52
|
+
- What happens when a partial failure occurs mid-operation?
|
|
53
|
+
- Are there race conditions or ordering dependencies across handlers?
|
|
54
|
+
|
|
55
|
+
### 4. Backward Compatibility
|
|
56
|
+
|
|
57
|
+
- Does this break any existing config formats or file structures?
|
|
58
|
+
- Will existing users' export → deploy workflows still work unchanged?
|
|
59
|
+
- Are old config shapes (from before this PR) still handled gracefully?
|
|
60
|
+
- Does any new export format, when re-deployed unchanged, produce unintended side effects?
|
|
61
|
+
|
|
62
|
+
### 5. Safety & Destructive Operations
|
|
63
|
+
|
|
64
|
+
- Are destructive operations (delete, overwrite) gated by `ALLOW_DELETE`?
|
|
65
|
+
- Is the safety contract enforced in code on **all** paths — not just the obvious ones?
|
|
66
|
+
- Could a no-op deploy (nothing changed) still cause mutations?
|
|
67
|
+
|
|
68
|
+
### 6. Tests
|
|
69
|
+
|
|
70
|
+
- Are unit tests present for new logic?
|
|
71
|
+
- Do tests actually test what their names claim? Verify the mock matches the test name.
|
|
72
|
+
- Are critical/risky paths covered — not just the happy path?
|
|
73
|
+
- Are E2E tests present or updated? If not, is there a documented reason?
|
|
74
|
+
- Is there a regression test for the exact failure scenario the PR is fixing?
|
|
75
|
+
- Are there idempotency tests — running the same operation twice produces no extra changes?
|
|
76
|
+
|
|
77
|
+
### 7. Docs & Examples
|
|
78
|
+
|
|
79
|
+
- Is there a new config shape or feature? Are examples updated?
|
|
80
|
+
- Are example files (`examples/`, `tenant.yaml`) updated to demonstrate new functionality?
|
|
81
|
+
- Is the README or relevant documentation updated?
|
|
82
|
+
- Are new config fields, env vars, or flags documented?
|
|
83
|
+
|
|
84
|
+
### 8. Code Quality
|
|
85
|
+
|
|
86
|
+
- Is the code readable and does it follow existing patterns?
|
|
87
|
+
- Are there unnecessary type casts (`as any`, `as Function`) that bypass type safety?
|
|
88
|
+
- Is there dead code, unused imports, or leftover debug statements?
|
|
89
|
+
- Is mutation of shared state (e.g. assets objects) clearly intentional and safe?
|
|
90
|
+
|
|
91
|
+
### 9. Performance
|
|
92
|
+
|
|
93
|
+
- Are there N+1 patterns (API calls inside loops)?
|
|
94
|
+
- Could this cause rate limiting for large tenants?
|
|
95
|
+
|
|
96
|
+
### 10. Security
|
|
97
|
+
|
|
98
|
+
- Are sensitive values (secrets, PEM keys, tokens) ever logged or exported?
|
|
99
|
+
- Is user-controlled input validated before use?
|
|
100
|
+
|
|
101
|
+
### 11. deploy-cli Specific — verify all of these
|
|
102
|
+
|
|
103
|
+
- Does the change work in **both** YAML and directory formats?
|
|
104
|
+
- Is the dry-run path correct? Dry-run must never mutate state on any affected path.
|
|
105
|
+
- Is keyword replacement (`@@KEY@@` / `##KEY##`) preserved correctly for new fields?
|
|
106
|
+
- Are write-only fields (secrets, key material) stripped on export and never written to disk?
|
|
107
|
+
- Are read-only/API-generated fields (`created_at`, `updated_at`, `id`, `fingerprint`) stripped on export and excluded from create/update payloads?
|
|
108
|
+
- Does a new handler implement all required methods: `getType`, `calcChanges`, `processChanges`, `validate`?
|
|
109
|
+
- Does the `identifiers` array include `name` (or a stable name-like field) as the primary key — not solely an auto-generated UUID `id`?
|
|
110
|
+
- Is handler `@order()` placed correctly relative to its dependencies?
|
|
111
|
+
- Is the JSON schema complete and accurate without being so permissive it passes invalid configs?
|
|
112
|
+
- Is sorted-key output stable? No unintended order changes introduced in exported configs.
|
|
113
|
+
|
|
114
|
+
### 12. EA / Entitlement-Gated (apply if feature requires a flag or entitlement to test)
|
|
115
|
+
|
|
116
|
+
- Which scenarios could not be tested? Are they explicitly listed in the PR description?
|
|
117
|
+
- For each untested scenario, are the API assumptions spelled out — not buried under "code path confirmed correct"?
|
|
118
|
+
- If a field is read from `this.existing` (populated via list) and used in a guard: has the list endpoint been verified to return that field?
|
|
119
|
+
- If schema uses `additionalProperties: false` on an EA object: has the full read shape been confirmed from a real API response?
|
|
120
|
+
- Is there a follow-up ticket to re-verify untested scenarios once entitlement is available?
|
|
121
|
+
|
|
122
|
+
### Final Gate — answer all four before approving
|
|
123
|
+
|
|
124
|
+
1. If I export and immediately re-deploy with no changes, is anything mutated?
|
|
125
|
+
2. If a mid-operation step fails, is the system left in a recoverable state?
|
|
126
|
+
3. Has the safety contract been verified in code for every path — not just described in the PR?
|
|
127
|
+
4. Are docs and examples updated so another engineer can use this feature without reading the code?
|
|
128
|
+
|
|
129
|
+
### Lessons Learned
|
|
130
|
+
|
|
131
|
+
- **Safety claim ≠ safety in code.** "Deletes are gated" in a PR description doesn't mean the code gates deletes on every path. Verify line by line.
|
|
132
|
+
- **Early-exit logic is subtle.** `A && B` vs `A` in a guard clause can be the difference between a no-op and data loss.
|
|
133
|
+
- **Mocks can lie.** A test named "should not delete when flag is off" may pass for the wrong reason. Always verify the stub is called (or not called) with `sinon.assert`.
|
|
134
|
+
- **Export format drift breaks imports.** A richer export shape can silently break the import path if the importer doesn't handle it. Check both sides.
|
|
135
|
+
- **"Code path confirmed correct" ≠ tested end-to-end.** If an entitlement blocked a scenario, say so explicitly and enumerate the unverified assumptions.
|