@aifabrix/builder 2.44.6 → 2.45.5
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/.cursor/rules/cli-layout.mdc +7 -3
- package/.cursor/rules/docs-rules.mdc +3 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-credential-real.yaml +1 -1
- package/integration/hubspot-test/test-artifacts/wizard-hubspot-env-vars.yaml +1 -1
- package/integration/hubspot-test/wizard-hubspot-e2e.yaml +1 -1
- package/jest.projects.js +100 -1
- package/lib/api/agent-metadata-validation.api.js +93 -0
- package/lib/api/audit-api-helpers.js +68 -0
- package/lib/api/audit.api.js +119 -0
- package/lib/api/auth-cache.api.js +44 -0
- package/lib/api/certificates.api.js +4 -4
- package/lib/api/credential.api.js +2 -2
- package/lib/api/dataplane-health.api.js +137 -0
- package/lib/api/dataplane-sync.api.js +27 -0
- package/lib/api/datasources-core.api.js +21 -18
- package/lib/api/datasources-extended.api.js +22 -22
- package/lib/api/dimensions.api.js +3 -0
- package/lib/api/external-systems.api.js +13 -13
- package/lib/api/governance-scenarios.api.js +39 -0
- package/lib/api/groups.api.js +79 -0
- package/lib/api/index.js +45 -2
- package/lib/api/pipeline.api.js +4 -4
- package/lib/api/protection.api.js +157 -0
- package/lib/api/records-bulk.api.js +30 -0
- package/lib/api/records-search-parse.js +49 -0
- package/lib/api/records-search.api.js +29 -0
- package/lib/api/types/agent-metadata-validation.types.js +67 -0
- package/lib/api/types/audit.types.js +72 -0
- package/lib/api/types/dataplane-health.types.js +27 -0
- package/lib/api/types/dimensions.types.js +2 -0
- package/lib/api/types/governance-scenarios.types.js +39 -0
- package/lib/api/types/protection.types.js +69 -0
- package/lib/api/types/records-bulk.types.js +35 -0
- package/lib/api/types/records-search.types.js +28 -0
- package/lib/api/user-groups.api.js +50 -0
- package/lib/api/users.api.js +95 -0
- package/lib/api/validation-run.api.js +3 -3
- package/lib/api/validation-runner.js +3 -3
- package/lib/api/wizard-platform.api.js +3 -3
- package/lib/api/wizard.api.js +21 -21
- package/lib/app/deploy.js +34 -19
- package/lib/app/helpers.js +3 -3
- package/lib/app/index.js +3 -3
- package/lib/app/register.js +7 -6
- package/lib/app/restart-display.js +52 -21
- package/lib/app/rotate-secret.js +7 -6
- package/lib/app/run-helpers.js +15 -8
- package/lib/app/run.js +57 -9
- package/lib/app/show-display.js +7 -0
- package/lib/app/show.js +87 -5
- package/lib/build/index.js +9 -5
- package/lib/cli/index.js +4 -2
- package/lib/cli/infra-guided.js +62 -28
- package/lib/cli/installation-log-command.js +73 -0
- package/lib/cli/setup-app.help.js +50 -2
- package/lib/cli/setup-app.js +44 -25
- package/lib/cli/setup-app.test-commands.js +80 -21
- package/lib/cli/setup-auth.js +124 -52
- package/lib/cli/setup-external-system.js +37 -3
- package/lib/cli/setup-infra-up-dataplane-action.js +111 -0
- package/lib/cli/setup-infra-up-platform-action.js +131 -0
- package/lib/cli/setup-infra.js +60 -119
- package/lib/cli/setup-platform.js +1 -1
- package/lib/cli/setup-utility-help-after.js +45 -0
- package/lib/cli/setup-utility-resolve.js +132 -0
- package/lib/cli/setup-utility.js +148 -116
- package/lib/commands/app-logs.js +81 -33
- package/lib/commands/auth-config.js +116 -18
- package/lib/commands/auth-status-dataplane-version.js +231 -0
- package/lib/commands/auth-status-display.js +94 -0
- package/lib/commands/auth-status.js +70 -6
- package/lib/commands/credential-push.js +10 -5
- package/lib/commands/datasource-load-export-cli.js +178 -0
- package/lib/commands/datasource-test-trust-cli.js +114 -0
- package/lib/commands/datasource-unified-test-cli.js +25 -17
- package/lib/commands/datasource-unified-test-cli.options.js +18 -3
- package/lib/commands/datasource-unified-test-e2e-cli-helpers.js +5 -5
- package/lib/commands/datasource-verify-audit-cli.js +96 -0
- package/lib/commands/datasource.js +68 -1
- package/lib/commands/dimension-value.js +43 -13
- package/lib/commands/dimension.js +38 -7
- package/lib/commands/identity-apply-sync.js +131 -0
- package/lib/commands/identity-group-cli.js +146 -0
- package/lib/commands/identity-membership-role-cache.js +175 -0
- package/lib/commands/identity-shared.js +94 -0
- package/lib/commands/identity-user-cli.js +158 -0
- package/lib/commands/identity.js +43 -0
- package/lib/commands/login.js +46 -16
- package/lib/commands/protection-cli-leaves.js +286 -0
- package/lib/commands/protection.js +50 -0
- package/lib/commands/repair.js +13 -3
- package/lib/commands/setup-modes.js +19 -6
- package/lib/commands/setup-prompts.js +41 -8
- package/lib/commands/setup.js +114 -9
- package/lib/commands/teardown.js +54 -5
- package/lib/commands/test-e2e-external.js +40 -9
- package/lib/commands/test-governance-cli-options.js +47 -0
- package/lib/commands/test-governance-command-action.js +55 -0
- package/lib/commands/test-governance-external.js +88 -0
- package/lib/commands/test-trust-cli-options.js +32 -0
- package/lib/commands/test-trust-command-action.js +118 -0
- package/lib/commands/test-trust-external.js +127 -0
- package/lib/commands/up-common.js +48 -14
- package/lib/commands/up-dataplane.js +21 -18
- package/lib/commands/up-miso.js +12 -8
- package/lib/commands/upload.js +38 -15
- package/lib/commands/wizard-core.js +3 -0
- package/lib/core/audit-logger.js +1 -34
- package/lib/core/config-admin-email.js +56 -0
- package/lib/core/config-device-dataplane.js +185 -0
- package/lib/core/config-normalize.js +60 -0
- package/lib/core/config-registered-controller-urls.js +54 -0
- package/lib/core/config.js +33 -50
- package/lib/core/secrets-ensure-infra.js +1 -1
- package/lib/core/secrets-env-content.js +86 -90
- package/lib/core/secrets-env-declarative-expand.js +170 -0
- package/lib/core/secrets-env-write.js +2 -0
- package/lib/core/secrets-load.js +106 -102
- package/lib/datasource/agent-trust-debug-log.js +45 -0
- package/lib/datasource/agent-trust-fetch.js +115 -0
- package/lib/datasource/agent-trust-map.js +101 -0
- package/lib/datasource/agent-trust-mock.js +44 -0
- package/lib/datasource/agent-trust-run.js +93 -0
- package/lib/datasource/agent-trust-timeout.js +25 -0
- package/lib/datasource/audit-evidence-display.js +80 -0
- package/lib/datasource/audit-evidence-extract-debug.js +103 -0
- package/lib/datasource/audit-evidence-extract.js +177 -0
- package/lib/datasource/audit-evidence-matrix-rows.js +158 -0
- package/lib/datasource/audit-evidence-operation-aliases.js +63 -0
- package/lib/datasource/audit-evidence-poll.js +109 -0
- package/lib/datasource/audit-evidence-run.js +177 -0
- package/lib/datasource/audit-evidence-verify-helpers.js +167 -0
- package/lib/datasource/audit-evidence-verify-matrix.js +177 -0
- package/lib/datasource/audit-evidence-verify.js +108 -0
- package/lib/datasource/bulk-loader-run-batches.js +88 -0
- package/lib/datasource/bulk-loader-service-retry.js +52 -0
- package/lib/datasource/bulk-loader-service.js +127 -0
- package/lib/datasource/capability/run-capability-dimension.js +2 -1
- package/lib/datasource/datasource-exporter-resolve.js +82 -0
- package/lib/datasource/datasource-exporter-service.js +144 -0
- package/lib/datasource/datasource-identifier.js +148 -0
- package/lib/datasource/datasource-load-export-context.js +92 -0
- package/lib/datasource/integration-context.js +6 -4
- package/lib/datasource/local-data-paths.js +145 -0
- package/lib/datasource/log-cleaner.js +241 -0
- package/lib/datasource/log-viewer.js +111 -6
- package/lib/datasource/record-file-parser.js +85 -0
- package/lib/datasource/record-mapper.js +129 -0
- package/lib/datasource/resolve-app.js +74 -55
- package/lib/datasource/test-e2e.js +26 -2
- package/lib/datasource/unified-validation-run-resolve.js +45 -12
- package/lib/datasource/unified-validation-run.js +43 -17
- package/lib/datasource/validate.js +37 -20
- package/lib/external-system/deploy.js +5 -1
- package/lib/external-system/test-system-level.js +25 -4
- package/lib/external-system/test.js +3 -1
- package/lib/governance/governance-pack-loader.js +70 -0
- package/lib/governance/governance-run-display.js +119 -0
- package/lib/identity/identity-apply-core.js +108 -0
- package/lib/identity/identity-apply-phases.js +244 -0
- package/lib/identity/identity-apply-service.js +77 -0
- package/lib/identity/identity-csv-parser.js +114 -0
- package/lib/internal/fs-real-sync.js +29 -1
- package/lib/internal/node-fs.js +2 -0
- package/lib/protection/auth-context.js +45 -0
- package/lib/protection/convert-batch.js +90 -0
- package/lib/protection/load.js +41 -0
- package/lib/protection/paths.js +158 -0
- package/lib/protection/preflight-datasource-ready.js +46 -0
- package/lib/protection/protection-create-probe-lines.js +142 -0
- package/lib/protection/protection-create-probes.js +95 -0
- package/lib/protection/protection-create-scaffold.js +281 -0
- package/lib/protection/protection-display-helpers.js +445 -0
- package/lib/protection/protection-display.js +215 -0
- package/lib/protection/protection-preset-registry.js +97 -0
- package/lib/protection/report-exit.js +62 -0
- package/lib/protection/resolve.js +202 -0
- package/lib/protection/run-commands-validate.js +130 -0
- package/lib/protection/run-commands.js +216 -0
- package/lib/protection/run-protection-create-helpers.js +134 -0
- package/lib/protection/run-protection-create-probes-flow.js +43 -0
- package/lib/protection/run-protection-create.js +191 -0
- package/lib/protection/scope.js +22 -0
- package/lib/protection/sync-after-upload.js +64 -0
- package/lib/protection/upload-batch.js +127 -0
- package/lib/protection/validate-batch.js +113 -0
- package/lib/protection/validate-local.js +65 -0
- package/lib/resolvers/dimension-file.js +29 -4
- package/lib/schema/application-schema.json +4 -0
- package/lib/schema/external-datasource.schema.json +47 -13
- package/lib/schema/flag-map-validation-run.json +2 -2
- package/lib/schema/infra.parameter.yaml +10 -0
- package/lib/schema/protection.schema.json +374 -0
- package/lib/schema/type/credential-field-registry.json +636 -0
- package/lib/schema/wizard-config.schema.json +1 -1
- package/lib/utils/agent-trust-run-display.js +231 -0
- package/lib/utils/agent-trust-run-exit.js +59 -0
- package/lib/utils/agent-trust-wait-ui.js +94 -0
- package/lib/utils/app-config-resolver.js +24 -1
- package/lib/utils/applications-config-defaults.js +206 -0
- package/lib/utils/auth-config-validator.js +2 -12
- package/lib/utils/bash-secret-env.js +1 -1
- package/lib/utils/cli-exit-codes.js +30 -0
- package/lib/utils/cli-sync-options.js +33 -0
- package/lib/utils/compose-generate-docker-compose.js +111 -6
- package/lib/utils/compose-generator.js +17 -8
- package/lib/utils/controller-url.js +50 -7
- package/lib/utils/credential-push-ui.js +90 -0
- package/lib/utils/dataplane-cli-version-gate.js +202 -0
- package/lib/utils/dataplane-cli-version-help.js +74 -0
- package/lib/utils/datasource-export-display.js +137 -0
- package/lib/utils/datasource-load-display.js +174 -0
- package/lib/utils/datasource-load-export-ui.js +120 -0
- package/lib/utils/datasource-test-run-capability-scope.js +3 -3
- package/lib/utils/datasource-test-run-display.js +3 -3
- package/lib/utils/datasource-test-run-tty-log.js +5 -1
- package/lib/utils/env-copy.js +99 -14
- package/lib/utils/env-template.js +5 -1
- package/lib/utils/health-check-url.js +18 -15
- package/lib/utils/health-check.js +7 -5
- package/lib/utils/help-builder.js +30 -24
- package/lib/utils/infra-optional-service-flags.js +69 -0
- package/lib/utils/installation-log-core.js +282 -0
- package/lib/utils/installation-log-record.js +237 -0
- package/lib/utils/installation-log.js +123 -0
- package/lib/utils/log-redaction.js +105 -0
- package/lib/utils/manifest-location.js +164 -0
- package/lib/utils/manifest-source-emit.js +184 -0
- package/lib/utils/paths.js +238 -89
- package/lib/utils/remote-secrets-loader.js +7 -1
- package/lib/utils/run-cli-flags.js +29 -0
- package/lib/utils/secrets-canonical.js +10 -3
- package/lib/utils/secrets-path.js +3 -4
- package/lib/utils/secrets-utils.js +20 -10
- package/lib/utils/semver-compare.js +127 -0
- package/lib/utils/system-builder-root.js +10 -2
- package/lib/utils/upload-pipeline-ui.js +91 -0
- package/lib/utils/url-declarative-public-base.js +80 -12
- package/lib/utils/url-declarative-resolve-build-urls.js +238 -0
- package/lib/utils/url-declarative-resolve-build.js +24 -393
- package/lib/utils/url-declarative-resolve-expand-token.js +189 -0
- package/lib/utils/url-declarative-resolve-load-doc.js +12 -3
- package/lib/utils/url-declarative-resolve-surface-state.js +102 -0
- package/lib/utils/url-declarative-resolve.js +47 -7
- package/lib/utils/url-declarative-runtime-base-path.js +21 -1
- package/lib/utils/urls-local-registry-scan.js +103 -0
- package/lib/utils/urls-local-registry.js +161 -90
- package/lib/utils/validation-poll-ui.js +14 -7
- package/lib/validation/wizard-config-validator.js +58 -19
- package/package.json +3 -1
- package/templates/applications/dataplane/application.yaml +4 -0
- package/templates/applications/dataplane/env.template +13 -1
- package/templates/applications/dataplane/rbac.yaml +4 -0
- package/templates/applications/miso-controller/application.yaml +2 -0
- package/templates/applications/miso-controller/env.template +27 -29
- package/templates/external-system/README.md.hbs +56 -8
- package/.npmrc.token +0 -1
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Phased execution for identity apply
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { getGroup, createGroup } = require('../api/groups.api');
|
|
10
|
+
const { createUser } = require('../api/users.api');
|
|
11
|
+
const { addUserToGroup } = require('../api/user-groups.api');
|
|
12
|
+
const { clearAuthCache } = require('../api/auth-cache.api');
|
|
13
|
+
const { fullSyncToDataplane } = require('../api/dataplane-sync.api');
|
|
14
|
+
const {
|
|
15
|
+
unwrapControllerData,
|
|
16
|
+
throwIfApiFailed,
|
|
17
|
+
assertSyncStatsOk,
|
|
18
|
+
apiErrorMessage,
|
|
19
|
+
findExistingUser
|
|
20
|
+
} = require('./identity-apply-core');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string} msg
|
|
24
|
+
* @returns {boolean}
|
|
25
|
+
*/
|
|
26
|
+
function isAlreadyMemberError(msg) {
|
|
27
|
+
return /already a member|409|Conflict/i.test(String(msg || ''));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @async
|
|
32
|
+
* @param {string} controllerUrl
|
|
33
|
+
* @param {Object} authConfig
|
|
34
|
+
* @param {{ name: string, displayName: string }} spec
|
|
35
|
+
* @param {boolean} dryRun
|
|
36
|
+
* @returns {Promise<{ id: string, created: boolean }>}
|
|
37
|
+
*/
|
|
38
|
+
async function ensureGroup(controllerUrl, authConfig, spec, dryRun) {
|
|
39
|
+
const name = spec.name;
|
|
40
|
+
const existingRes = await getGroup(controllerUrl, authConfig, name);
|
|
41
|
+
const existing = unwrapControllerData(existingRes);
|
|
42
|
+
if (existing && existing.id) {
|
|
43
|
+
return { id: existing.id, created: false };
|
|
44
|
+
}
|
|
45
|
+
if (dryRun) {
|
|
46
|
+
return { id: `(dry-run:${name})`, created: true };
|
|
47
|
+
}
|
|
48
|
+
const createdRes = await createGroup(controllerUrl, authConfig, {
|
|
49
|
+
name,
|
|
50
|
+
displayName: spec.displayName || name
|
|
51
|
+
});
|
|
52
|
+
throwIfApiFailed(createdRes);
|
|
53
|
+
const created = unwrapControllerData(createdRes);
|
|
54
|
+
return { id: created.id, created: true };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @async
|
|
59
|
+
* @param {string} controllerUrl
|
|
60
|
+
* @param {Object} authConfig
|
|
61
|
+
* @param {Object} userSpec
|
|
62
|
+
* @param {boolean} dryRun
|
|
63
|
+
* @returns {Promise<{ id: string, created: boolean }>}
|
|
64
|
+
*/
|
|
65
|
+
async function ensureUser(controllerUrl, authConfig, userSpec, dryRun) {
|
|
66
|
+
const existing = await findExistingUser(controllerUrl, authConfig, userSpec);
|
|
67
|
+
if (existing && existing.id) {
|
|
68
|
+
return { id: existing.id, created: false };
|
|
69
|
+
}
|
|
70
|
+
if (dryRun) {
|
|
71
|
+
return { id: `(dry-run:${userSpec.email})`, created: true };
|
|
72
|
+
}
|
|
73
|
+
const body = {
|
|
74
|
+
email: userSpec.email,
|
|
75
|
+
firstName: userSpec.firstName,
|
|
76
|
+
lastName: userSpec.lastName,
|
|
77
|
+
displayName: userSpec.displayName,
|
|
78
|
+
username: userSpec.username,
|
|
79
|
+
status: 'active'
|
|
80
|
+
};
|
|
81
|
+
Object.keys(body).forEach((k) => {
|
|
82
|
+
if (body[k] === undefined || body[k] === '') {
|
|
83
|
+
delete body[k];
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
const createdRes = await createUser(controllerUrl, authConfig, body);
|
|
87
|
+
throwIfApiFailed(createdRes);
|
|
88
|
+
const created = unwrapControllerData(createdRes);
|
|
89
|
+
return { id: created.id, created: true };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* @async
|
|
94
|
+
* @param {string} controllerUrl
|
|
95
|
+
* @param {Object} authConfig
|
|
96
|
+
* @param {Map} groups
|
|
97
|
+
* @param {boolean} dryRun
|
|
98
|
+
* @returns {Promise<{ map: Map<string, string>, created: number, processed: number }>}
|
|
99
|
+
*/
|
|
100
|
+
async function applyGroupsPhase(controllerUrl, authConfig, groups, dryRun) {
|
|
101
|
+
const map = new Map();
|
|
102
|
+
let created = 0;
|
|
103
|
+
let processed = 0;
|
|
104
|
+
for (const spec of groups.values()) {
|
|
105
|
+
const out = await ensureGroup(controllerUrl, authConfig, spec, dryRun);
|
|
106
|
+
map.set(spec.name, out.id);
|
|
107
|
+
processed += 1;
|
|
108
|
+
if (out.created) {
|
|
109
|
+
created += 1;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return { map, created, processed };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @async
|
|
117
|
+
* @param {string} controllerUrl
|
|
118
|
+
* @param {Object} authConfig
|
|
119
|
+
* @param {Map} users
|
|
120
|
+
* @param {boolean} dryRun
|
|
121
|
+
* @returns {Promise<{ map: Map<string, string>, created: number, processed: number }>}
|
|
122
|
+
*/
|
|
123
|
+
async function applyUsersPhase(controllerUrl, authConfig, users, dryRun) {
|
|
124
|
+
const map = new Map();
|
|
125
|
+
let created = 0;
|
|
126
|
+
let processed = 0;
|
|
127
|
+
for (const [userKey, userSpec] of users.entries()) {
|
|
128
|
+
const out = await ensureUser(controllerUrl, authConfig, userSpec, dryRun);
|
|
129
|
+
map.set(userKey, out.id);
|
|
130
|
+
processed += 1;
|
|
131
|
+
if (out.created) {
|
|
132
|
+
created += 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return { map, created, processed };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @async
|
|
140
|
+
* @param {string} controllerUrl
|
|
141
|
+
* @param {Object} authConfig
|
|
142
|
+
* @param {Object[]} memberships
|
|
143
|
+
* @param {Map<string, string>} userIdByKey
|
|
144
|
+
* @param {Map<string, string>} groupIdByName
|
|
145
|
+
* @param {boolean} dryRun
|
|
146
|
+
* @returns {Promise<{ created: number, processed: number, skipped: number }>}
|
|
147
|
+
*/
|
|
148
|
+
/**
|
|
149
|
+
* @async
|
|
150
|
+
* @param {string} controllerUrl
|
|
151
|
+
* @param {Object} authConfig
|
|
152
|
+
* @param {Object} m
|
|
153
|
+
* @param {Map<string, string>} userIdByKey
|
|
154
|
+
* @param {Map<string, string>} groupIdByName
|
|
155
|
+
* @param {boolean} dryRun
|
|
156
|
+
* @returns {Promise<'created'|'skipped'|'dry'>}
|
|
157
|
+
*/
|
|
158
|
+
async function applyOneMembership(controllerUrl, authConfig, m, userIdByKey, groupIdByName, dryRun) {
|
|
159
|
+
const userId = userIdByKey.get(m.userKey);
|
|
160
|
+
const groupId = groupIdByName.get(m.groupName);
|
|
161
|
+
if (!userId || !groupId) {
|
|
162
|
+
return 'skipped';
|
|
163
|
+
}
|
|
164
|
+
if (dryRun) {
|
|
165
|
+
return 'dry';
|
|
166
|
+
}
|
|
167
|
+
const res = await addUserToGroup(controllerUrl, authConfig, userId, groupId, {});
|
|
168
|
+
if (res && res.success !== false) {
|
|
169
|
+
return 'created';
|
|
170
|
+
}
|
|
171
|
+
if (isAlreadyMemberError(apiErrorMessage(res))) {
|
|
172
|
+
return 'skipped';
|
|
173
|
+
}
|
|
174
|
+
throw new Error(apiErrorMessage(res));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function applyMembershipsPhase(
|
|
178
|
+
controllerUrl,
|
|
179
|
+
authConfig,
|
|
180
|
+
memberships,
|
|
181
|
+
userIdByKey,
|
|
182
|
+
groupIdByName,
|
|
183
|
+
dryRun
|
|
184
|
+
) {
|
|
185
|
+
let created = 0;
|
|
186
|
+
let processed = 0;
|
|
187
|
+
let skipped = 0;
|
|
188
|
+
for (const m of memberships) {
|
|
189
|
+
const userId = userIdByKey.get(m.userKey);
|
|
190
|
+
const groupId = groupIdByName.get(m.groupName);
|
|
191
|
+
if (!userId || !groupId) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
processed += 1;
|
|
195
|
+
const outcome = await applyOneMembership(
|
|
196
|
+
controllerUrl,
|
|
197
|
+
authConfig,
|
|
198
|
+
m,
|
|
199
|
+
userIdByKey,
|
|
200
|
+
groupIdByName,
|
|
201
|
+
dryRun
|
|
202
|
+
);
|
|
203
|
+
if (outcome === 'created' || outcome === 'dry') {
|
|
204
|
+
created += 1;
|
|
205
|
+
} else if (outcome === 'skipped') {
|
|
206
|
+
skipped += 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return { created, processed, skipped };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* @async
|
|
214
|
+
* @param {string} controllerUrl
|
|
215
|
+
* @param {Object} authConfig
|
|
216
|
+
* @param {Object} opts
|
|
217
|
+
* @returns {Promise<Object|null>}
|
|
218
|
+
*/
|
|
219
|
+
async function maybePurgeAndSync(controllerUrl, authConfig, opts) {
|
|
220
|
+
if (opts.dryRun) {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
if (opts.purgeCache) {
|
|
224
|
+
const cacheRes = await clearAuthCache(controllerUrl, authConfig);
|
|
225
|
+
throwIfApiFailed(cacheRes);
|
|
226
|
+
}
|
|
227
|
+
if (!opts.sync || !opts.envKey) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const syncRes = await fullSyncToDataplane(controllerUrl, authConfig, opts.envKey);
|
|
231
|
+
throwIfApiFailed(syncRes);
|
|
232
|
+
const stats = unwrapControllerData(syncRes);
|
|
233
|
+
assertSyncStatsOk(stats, { allowEmptySync: opts.allowEmptySync });
|
|
234
|
+
return stats;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
module.exports = {
|
|
238
|
+
ensureGroup,
|
|
239
|
+
ensureUser,
|
|
240
|
+
applyGroupsPhase,
|
|
241
|
+
applyUsersPhase,
|
|
242
|
+
applyMembershipsPhase,
|
|
243
|
+
maybePurgeAndSync
|
|
244
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Bulk identity apply (CSV) against miso-controller
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { parseUsersCsvFile, buildApplyPlanFromRows } = require('./identity-csv-parser');
|
|
10
|
+
const {
|
|
11
|
+
applyGroupsPhase,
|
|
12
|
+
applyUsersPhase,
|
|
13
|
+
applyMembershipsPhase,
|
|
14
|
+
maybePurgeAndSync
|
|
15
|
+
} = require('./identity-apply-phases');
|
|
16
|
+
|
|
17
|
+
const {
|
|
18
|
+
unwrapControllerData,
|
|
19
|
+
apiErrorMessage,
|
|
20
|
+
throwIfApiFailed,
|
|
21
|
+
findExistingUser
|
|
22
|
+
} = require('./identity-apply-core');
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @async
|
|
26
|
+
* @param {string} controllerUrl
|
|
27
|
+
* @param {Object} authConfig
|
|
28
|
+
* @param {Object} options
|
|
29
|
+
* @returns {Promise<Object>}
|
|
30
|
+
*/
|
|
31
|
+
async function runIdentityApply(controllerUrl, authConfig, options) {
|
|
32
|
+
const { rows } = parseUsersCsvFile(options.filePath, options.filterPrefix);
|
|
33
|
+
if (!rows.length) {
|
|
34
|
+
throw new Error('No data rows to apply after parsing CSV');
|
|
35
|
+
}
|
|
36
|
+
const plan = buildApplyPlanFromRows(rows);
|
|
37
|
+
const dryRun = Boolean(options.dryRun);
|
|
38
|
+
|
|
39
|
+
const groupPhase = await applyGroupsPhase(controllerUrl, authConfig, plan.groups, dryRun);
|
|
40
|
+
const userPhase = await applyUsersPhase(controllerUrl, authConfig, plan.users, dryRun);
|
|
41
|
+
const memPhase = await applyMembershipsPhase(
|
|
42
|
+
controllerUrl,
|
|
43
|
+
authConfig,
|
|
44
|
+
plan.memberships,
|
|
45
|
+
userPhase.map,
|
|
46
|
+
groupPhase.map,
|
|
47
|
+
dryRun
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const syncStats = await maybePurgeAndSync(controllerUrl, authConfig, {
|
|
51
|
+
dryRun,
|
|
52
|
+
purgeCache: Boolean(options.purgeCache),
|
|
53
|
+
sync: Boolean(options.sync),
|
|
54
|
+
envKey: options.envKey,
|
|
55
|
+
allowEmptySync: Boolean(options.allowEmptySync)
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
groupsProcessed: groupPhase.processed,
|
|
60
|
+
groupsCreated: groupPhase.created,
|
|
61
|
+
usersProcessed: userPhase.processed,
|
|
62
|
+
usersCreated: userPhase.created,
|
|
63
|
+
membershipsProcessed: memPhase.processed,
|
|
64
|
+
membershipsCreated: memPhase.created,
|
|
65
|
+
membershipsSkipped: memPhase.skipped,
|
|
66
|
+
dryRun,
|
|
67
|
+
syncStats
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = {
|
|
72
|
+
unwrapControllerData,
|
|
73
|
+
apiErrorMessage,
|
|
74
|
+
throwIfApiFailed,
|
|
75
|
+
runIdentityApply,
|
|
76
|
+
findExistingUser
|
|
77
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Parse controller users.csv-style files for identity apply
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { readFileSync } = require('../internal/fs-real-sync');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parse one CSV line respecting double-quoted fields.
|
|
13
|
+
* @param {string} line
|
|
14
|
+
* @returns {string[]}
|
|
15
|
+
*/
|
|
16
|
+
function parseCsvLine(line) {
|
|
17
|
+
const out = [];
|
|
18
|
+
let cur = '';
|
|
19
|
+
let inQuotes = false;
|
|
20
|
+
for (let i = 0; i < line.length; i += 1) {
|
|
21
|
+
const ch = line[i];
|
|
22
|
+
if (ch === '"') {
|
|
23
|
+
if (inQuotes && line[i + 1] === '"') {
|
|
24
|
+
cur += '"';
|
|
25
|
+
i += 1;
|
|
26
|
+
} else {
|
|
27
|
+
inQuotes = !inQuotes;
|
|
28
|
+
}
|
|
29
|
+
} else if (ch === ',' && !inQuotes) {
|
|
30
|
+
out.push(cur.trim());
|
|
31
|
+
cur = '';
|
|
32
|
+
} else {
|
|
33
|
+
cur += ch;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
out.push(cur.trim());
|
|
37
|
+
return out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} filePath
|
|
42
|
+
* @param {string} [filterPrefix]
|
|
43
|
+
* @returns {{ headers: string[], rows: Object[] }}
|
|
44
|
+
*/
|
|
45
|
+
function parseUsersCsvFile(filePath, filterPrefix = '') {
|
|
46
|
+
const raw = readFileSync(filePath, 'utf8');
|
|
47
|
+
const lines = raw.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
48
|
+
if (lines.length < 2) {
|
|
49
|
+
throw new Error('CSV must include a header row and at least one data row');
|
|
50
|
+
}
|
|
51
|
+
const headers = parseCsvLine(lines[0]);
|
|
52
|
+
const prefix = String(filterPrefix || '').trim();
|
|
53
|
+
const rows = [];
|
|
54
|
+
for (let i = 1; i < lines.length; i += 1) {
|
|
55
|
+
const cells = parseCsvLine(lines[i]);
|
|
56
|
+
const row = {};
|
|
57
|
+
headers.forEach((h, idx) => {
|
|
58
|
+
row[h] = cells[idx] !== undefined ? cells[idx] : '';
|
|
59
|
+
});
|
|
60
|
+
const id = String(row.Id || row.id || '').trim();
|
|
61
|
+
if (prefix && id && !id.startsWith(prefix)) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
rows.push(row);
|
|
65
|
+
}
|
|
66
|
+
return { headers, rows };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Build unique entities from CSV rows (users.csv shape).
|
|
71
|
+
* @param {Object[]} rows
|
|
72
|
+
* @returns {{ groups: Map<string, { name: string, displayName: string }>, users: Map<string, Object>, memberships: { userKey: string, groupName: string }[] }}
|
|
73
|
+
*/
|
|
74
|
+
// eslint-disable-next-line complexity -- row shape varies; logic is linear per field
|
|
75
|
+
function buildApplyPlanFromRows(rows) {
|
|
76
|
+
const groups = new Map();
|
|
77
|
+
const users = new Map();
|
|
78
|
+
const memberships = [];
|
|
79
|
+
|
|
80
|
+
for (const row of rows) {
|
|
81
|
+
const csvId = String(row.Id || row.id || '').trim();
|
|
82
|
+
const email = String(row.Email || row.email || '').trim();
|
|
83
|
+
if (!email) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const userKey = csvId || email;
|
|
87
|
+
const groupName = String(row.GroupId || row.groupId || '').trim();
|
|
88
|
+
const groupDisplay = String(row.GroupName || row.groupName || groupName).trim();
|
|
89
|
+
if (groupName) {
|
|
90
|
+
if (!groups.has(groupName)) {
|
|
91
|
+
groups.set(groupName, { name: groupName, displayName: groupDisplay || groupName });
|
|
92
|
+
}
|
|
93
|
+
memberships.push({ userKey, groupName });
|
|
94
|
+
}
|
|
95
|
+
if (!users.has(userKey)) {
|
|
96
|
+
users.set(userKey, {
|
|
97
|
+
csvId,
|
|
98
|
+
email,
|
|
99
|
+
firstName: String(row.FirstName || row.firstName || '').trim() || undefined,
|
|
100
|
+
lastName: String(row.LastName || row.lastName || '').trim() || undefined,
|
|
101
|
+
displayName: String(row.DisplayName || row.displayName || '').trim() || undefined,
|
|
102
|
+
username: String(row.UserPrincipalName || row.userPrincipalName || row.username || '').trim() || undefined
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return { groups, users, memberships };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = {
|
|
111
|
+
parseCsvLine,
|
|
112
|
+
parseUsersCsvFile,
|
|
113
|
+
buildApplyPlanFromRows
|
|
114
|
+
};
|
|
@@ -93,6 +93,31 @@ function readdirSync(p, opts) {
|
|
|
93
93
|
return delegate('readdirSync', opts !== undefined ? [p, opts] : [p]);
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
/**
|
|
97
|
+
* @param {string} prefix
|
|
98
|
+
* @returns {string}
|
|
99
|
+
*/
|
|
100
|
+
function mkdtempSync(prefix) {
|
|
101
|
+
return delegate('mkdtempSync', [prefix]);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @param {string} p
|
|
106
|
+
* @param {import('node:fs').RmOptions} [opts]
|
|
107
|
+
* @returns {void}
|
|
108
|
+
*/
|
|
109
|
+
function rmSync(p, opts) {
|
|
110
|
+
return delegate('rmSync', opts !== undefined ? [p, opts] : [p]);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {string} p
|
|
115
|
+
* @returns {void}
|
|
116
|
+
*/
|
|
117
|
+
function unlinkSync(p) {
|
|
118
|
+
return delegate('unlinkSync', [p]);
|
|
119
|
+
}
|
|
120
|
+
|
|
96
121
|
module.exports = {
|
|
97
122
|
existsSync,
|
|
98
123
|
readFileSync,
|
|
@@ -100,5 +125,8 @@ module.exports = {
|
|
|
100
125
|
writeFileSync,
|
|
101
126
|
mkdirSync,
|
|
102
127
|
statSync,
|
|
103
|
-
readdirSync
|
|
128
|
+
readdirSync,
|
|
129
|
+
mkdtempSync,
|
|
130
|
+
rmSync,
|
|
131
|
+
unlinkSync
|
|
104
132
|
};
|
package/lib/internal/node-fs.js
CHANGED
|
@@ -72,6 +72,7 @@ function buildBoundFs() {
|
|
|
72
72
|
mkdirSync: snap.mkdirSync,
|
|
73
73
|
readdirSync: snap.readdirSync,
|
|
74
74
|
statSync: snap.statSync,
|
|
75
|
+
renameSync: snap.renameSync,
|
|
75
76
|
watch: (...args) => live.watch(...args),
|
|
76
77
|
promises: boundPromises
|
|
77
78
|
};
|
|
@@ -83,6 +84,7 @@ function buildBoundFs() {
|
|
|
83
84
|
mkdirSync: bindSync(rf, 'mkdirSync'),
|
|
84
85
|
readdirSync: bindSync(rf, 'readdirSync'),
|
|
85
86
|
statSync: bindSync(rf, 'statSync'),
|
|
87
|
+
renameSync: bindSync(rf, 'renameSync'),
|
|
86
88
|
watch: (...args) => live.watch(...args),
|
|
87
89
|
promises: boundPromises
|
|
88
90
|
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Dataplane auth/url resolution for protection commands.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const { resolveControllerUrl } = require('../utils/controller-url');
|
|
10
|
+
const { resolveEnvironment } = require('../core/config');
|
|
11
|
+
const { getDeviceOnlyAuth, getDeploymentAuth } = require('../utils/token-manager');
|
|
12
|
+
const { resolveDataplaneUrl } = require('../utils/dataplane-resolver');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {Object} [options]
|
|
16
|
+
* @param {string} [options.env]
|
|
17
|
+
* @returns {Promise<{ environment: string, controllerUrl: string, dataplaneUrl: string, authConfig: Object }>}
|
|
18
|
+
*/
|
|
19
|
+
async function resolveProtectionDataplaneContext(options = {}) {
|
|
20
|
+
const environment = options.env || (await resolveEnvironment());
|
|
21
|
+
const controllerUrl = await resolveControllerUrl();
|
|
22
|
+
if (!controllerUrl) {
|
|
23
|
+
throw new Error('Controller URL is required. Run "aifabrix login" first.');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let authConfig;
|
|
27
|
+
try {
|
|
28
|
+
authConfig = await getDeviceOnlyAuth(controllerUrl);
|
|
29
|
+
} catch {
|
|
30
|
+
authConfig = await getDeploymentAuth(controllerUrl, environment, 'dataplane');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!authConfig?.token) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'Authentication required. Run "aifabrix login" and try again.'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const dataplaneUrl = await resolveDataplaneUrl(controllerUrl, environment, authConfig);
|
|
40
|
+
return { environment, controllerUrl, dataplaneUrl, authConfig };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
resolveProtectionDataplaneContext
|
|
45
|
+
};
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Convert all protection manifests in `.protection/` between JSON and YAML.
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { existsSync, unlinkSync } = require('../internal/fs-real-sync');
|
|
11
|
+
const { getProtectionRoot } = require('./paths');
|
|
12
|
+
const { listProtectionManifestPaths } = require('./resolve');
|
|
13
|
+
const { promptConfirm, targetFileName, convertOneFile } = require('../commands/convert');
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string} root
|
|
17
|
+
* @param {string} format
|
|
18
|
+
* @returns {string[]}
|
|
19
|
+
*/
|
|
20
|
+
function listFilesNeedingConvert(root, format) {
|
|
21
|
+
const targetExt = format === 'json' ? '.json' : '.yaml';
|
|
22
|
+
return listProtectionManifestPaths(root).filter((p) => {
|
|
23
|
+
return path.extname(p).toLowerCase() !== targetExt;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {string[]} toConvert
|
|
29
|
+
* @param {string} format
|
|
30
|
+
* @param {boolean} force
|
|
31
|
+
* @returns {Promise<void>}
|
|
32
|
+
*/
|
|
33
|
+
async function confirmProtectionConvert(toConvert, format, force) {
|
|
34
|
+
if (force) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const lines = toConvert.map((p) => ` • ${path.basename(p)} → ${targetFileName(p, format)}`);
|
|
38
|
+
const confirmed = await promptConfirm(
|
|
39
|
+
`Convert all protection manifests to ${format}?\n${lines.join('\n')}\nAre you sure? (y/N) `
|
|
40
|
+
);
|
|
41
|
+
if (!confirmed) {
|
|
42
|
+
throw new Error('Convert cancelled.');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} root
|
|
48
|
+
* @param {string[]} toConvert
|
|
49
|
+
* @param {string} format
|
|
50
|
+
* @returns {{ converted: string[], deleted: string[] }}
|
|
51
|
+
*/
|
|
52
|
+
function convertProtectionFiles(root, toConvert, format) {
|
|
53
|
+
const converted = [];
|
|
54
|
+
const deleted = [];
|
|
55
|
+
for (const sourcePath of toConvert) {
|
|
56
|
+
const targetPath = path.join(root, targetFileName(sourcePath, format));
|
|
57
|
+
convertOneFile(sourcePath, targetPath, format);
|
|
58
|
+
converted.push(targetPath);
|
|
59
|
+
if (path.normalize(sourcePath) !== path.normalize(targetPath)) {
|
|
60
|
+
unlinkSync(sourcePath);
|
|
61
|
+
deleted.push(sourcePath);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return { converted, deleted };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {string} format - `yaml` | `json`
|
|
69
|
+
* @param {Object} opts
|
|
70
|
+
* @returns {Promise<{ converted: string[], deleted: string[] }>}
|
|
71
|
+
*/
|
|
72
|
+
async function runConvertProtectionBatch(format, opts = {}) {
|
|
73
|
+
const root = opts.root || getProtectionRoot();
|
|
74
|
+
if (!existsSync(root)) {
|
|
75
|
+
throw new Error(`Protection folder not found: ${root}`);
|
|
76
|
+
}
|
|
77
|
+
const toConvert = listFilesNeedingConvert(root, format);
|
|
78
|
+
if (!toConvert.length) {
|
|
79
|
+
return { converted: [], deleted: [] };
|
|
80
|
+
}
|
|
81
|
+
await confirmProtectionConvert(toConvert, format, opts.force === true);
|
|
82
|
+
return convertProtectionFiles(root, toConvert, format);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
runConvertProtectionBatch,
|
|
87
|
+
listFilesNeedingConvert,
|
|
88
|
+
confirmProtectionConvert,
|
|
89
|
+
convertProtectionFiles
|
|
90
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Load protection manifest YAML files (v1).
|
|
3
|
+
* @author AI Fabrix Team
|
|
4
|
+
* @version 1.0.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const { existsSync, readFileSync } = require('../internal/fs-real-sync');
|
|
11
|
+
const { yamlToJson } = require('../utils/config-format');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} manifestPath - Absolute path to `.yaml` manifest
|
|
15
|
+
* @returns {Object} Parsed manifest object
|
|
16
|
+
*/
|
|
17
|
+
function loadProtectionManifest(manifestPath) {
|
|
18
|
+
const p = path.resolve(String(manifestPath || '').trim());
|
|
19
|
+
if (!p) {
|
|
20
|
+
throw new Error('Protection manifest path is required');
|
|
21
|
+
}
|
|
22
|
+
if (!existsSync(p)) {
|
|
23
|
+
throw new Error(`Protection manifest not found: ${p}`);
|
|
24
|
+
}
|
|
25
|
+
const ext = path.extname(p).toLowerCase();
|
|
26
|
+
if (ext !== '.yaml' && ext !== '.yml' && ext !== '.json') {
|
|
27
|
+
throw new Error(
|
|
28
|
+
`Protection manifest must be .yaml, .yml, or .json (got ${ext || '(none)'}).`
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
const content = readFileSync(p, 'utf8');
|
|
32
|
+
if (ext === '.json') {
|
|
33
|
+
const parsed = JSON.parse(content);
|
|
34
|
+
return parsed === undefined || parsed === null ? {} : parsed;
|
|
35
|
+
}
|
|
36
|
+
return yamlToJson(content);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
module.exports = {
|
|
40
|
+
loadProtectionManifest
|
|
41
|
+
};
|