n8nac 2.3.5 → 2.3.6
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/README.md +4 -24
- package/dist/commands/base.d.ts.map +1 -1
- package/dist/commands/base.js +36 -90
- package/dist/commands/base.js.map +1 -1
- package/dist/commands/update-ai.d.ts.map +1 -1
- package/dist/commands/update-ai.js +27 -12
- package/dist/commands/update-ai.js.map +1 -1
- package/dist/index.js +19 -271
- package/dist/index.js.map +1 -1
- package/dist/lib.d.ts +1 -2
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +0 -1
- package/dist/lib.js.map +1 -1
- package/dist/services/config-service.d.ts +1 -155
- package/dist/services/config-service.d.ts.map +1 -1
- package/dist/services/config-service.js +53 -992
- package/dist/services/config-service.js.map +1 -1
- package/package.json +2 -2
- package/dist/services/workspace-migration-facade.d.ts +0 -21
- package/dist/services/workspace-migration-facade.d.ts.map +0 -1
- package/dist/services/workspace-migration-facade.js +0 -28
- package/dist/services/workspace-migration-facade.js.map +0 -1
|
@@ -1,83 +1,48 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import Conf from 'conf';
|
|
4
3
|
import { N8nConfigurationService, N8nRuntimeOrchestrator, } from '@n8n-as-code/n8n-manager-core';
|
|
5
|
-
import { N8nApiClient, createCanonicalInstanceIdentifier, createInstanceIdentifier, createInstanceUserIdentifier,
|
|
4
|
+
import { N8nApiClient, createCanonicalInstanceIdentifier, createInstanceIdentifier, createInstanceUserIdentifier, isCanonicalInstanceIdentifier, isCanonicalInstanceUserIdentifier, isCanonicalUserInstanceIdentifier, resolveInstanceIdentifier, resolveN8nIdentity as resolveN8nIdentityFromApi } from '../core/index.js';
|
|
6
5
|
const DEFAULT_SYNC_FOLDER = 'workflows';
|
|
7
6
|
export class ConfigService {
|
|
8
7
|
manager;
|
|
9
8
|
runtime;
|
|
10
9
|
workspaceRoot;
|
|
11
10
|
constructor(workspaceRoot) {
|
|
12
|
-
|
|
11
|
+
const testWorkspaceRoot = process.env.NODE_ENV === 'test'
|
|
12
|
+
? process.env.N8NAC_TEST_WORKSPACE_ROOT?.trim()
|
|
13
|
+
: undefined;
|
|
14
|
+
this.workspaceRoot = workspaceRoot ? path.resolve(workspaceRoot) : testWorkspaceRoot ? path.resolve(testWorkspaceRoot) : this.findConfigRoot(process.cwd());
|
|
13
15
|
this.manager = new N8nConfigurationService();
|
|
14
16
|
this.runtime = new N8nRuntimeOrchestrator({ configuration: this.manager });
|
|
15
17
|
}
|
|
16
18
|
getLocalConfig(environmentNameOrId) {
|
|
17
19
|
try {
|
|
18
|
-
|
|
19
|
-
return this.environmentToLocalConfig(this.resolveEnvironment(environmentNameOrId));
|
|
20
|
-
}
|
|
21
|
-
return this.contextToLocalConfig(this.resolveWorkspaceContext());
|
|
20
|
+
return this.environmentToLocalConfig(this.resolveEnvironment(environmentNameOrId));
|
|
22
21
|
}
|
|
23
22
|
catch {
|
|
24
|
-
|
|
25
|
-
return this.contextToLocalConfig(this.resolveWorkspaceContext());
|
|
26
|
-
}
|
|
27
|
-
catch {
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
23
|
+
return {};
|
|
30
24
|
}
|
|
31
25
|
}
|
|
32
26
|
getWorkspaceConfig() {
|
|
33
|
-
const legacyPlan = this.detectLegacyWorkspaceConfig();
|
|
34
|
-
if (legacyPlan) {
|
|
35
|
-
throw new Error(`Unsupported legacy n8n workspace config at ${legacyPlan.configPath}. ` +
|
|
36
|
-
'Run `n8nac workspace migrate --json` to inspect it, then `n8nac workspace migrate --write` to migrate it after confirmation.');
|
|
37
|
-
}
|
|
38
27
|
const persisted = this.readWorkspaceConfigFile();
|
|
39
|
-
if (persisted.version === 4) {
|
|
40
|
-
const instances = this.listInstances();
|
|
41
|
-
const effective = tryResolve(() => this.resolveEnvironment());
|
|
42
|
-
const environmentTargets = persisted.environmentTargets.map((target) => this.environmentTargetToSnapshot(target));
|
|
43
|
-
const environments = persisted.environments.map((environment) => this.environmentToSnapshot(environment));
|
|
44
|
-
return {
|
|
45
|
-
version: 4,
|
|
46
|
-
activeEnvironmentId: persisted.activeEnvironmentId,
|
|
47
|
-
activeInstanceId: effective?.activeInstanceId,
|
|
48
|
-
activeEnvironment: effective?.environment,
|
|
49
|
-
environmentTargets,
|
|
50
|
-
environments,
|
|
51
|
-
instances,
|
|
52
|
-
...(effective ? this.environmentToLocalConfig(effective) : {}),
|
|
53
|
-
sourceKind: effective?.sourceKind,
|
|
54
|
-
environmentTargetId: effective?.environmentTargetId,
|
|
55
|
-
environmentTargetName: effective?.environmentTargetName,
|
|
56
|
-
apiKeyAvailable: effective?.apiKeyAvailable,
|
|
57
|
-
credentialSource: effective?.apiKeySource,
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
const overrides = this.manager.readWorkspaceOverrides(this.workspaceRoot);
|
|
61
28
|
const instances = this.listInstances();
|
|
62
|
-
const effective = tryResolve(() => this.
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const activeProfile = effective ? this.contextToInstanceProfile(effective) : active;
|
|
66
|
-
const resolvedSyncFolder = overrides.syncFolder || effective?.syncFolder;
|
|
67
|
-
const resolvedProjectName = overrides.projectName || activeProfile?.projectName;
|
|
29
|
+
const effective = tryResolve(() => this.resolveEnvironment());
|
|
30
|
+
const environmentTargets = persisted.environmentTargets.map((target) => this.environmentTargetToSnapshot(target));
|
|
31
|
+
const environments = persisted.environments.map((environment) => this.environmentToSnapshot(environment));
|
|
68
32
|
return {
|
|
69
|
-
version:
|
|
70
|
-
|
|
33
|
+
version: 4,
|
|
34
|
+
activeEnvironmentId: persisted.activeEnvironmentId,
|
|
35
|
+
activeInstanceId: effective?.activeInstanceId,
|
|
36
|
+
activeEnvironment: effective?.environment,
|
|
37
|
+
environmentTargets,
|
|
38
|
+
environments,
|
|
71
39
|
instances,
|
|
72
|
-
...this.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
customNodesPath: overrides.customNodesPath || activeProfile?.customNodesPath,
|
|
79
|
-
workflowDir: undefined,
|
|
80
|
-
}),
|
|
40
|
+
...(effective ? this.environmentToLocalConfig(effective) : {}),
|
|
41
|
+
sourceKind: effective?.sourceKind,
|
|
42
|
+
environmentTargetId: effective?.environmentTargetId,
|
|
43
|
+
environmentTargetName: effective?.environmentTargetName,
|
|
44
|
+
apiKeyAvailable: effective?.apiKeyAvailable,
|
|
45
|
+
credentialSource: effective?.apiKeySource,
|
|
81
46
|
};
|
|
82
47
|
}
|
|
83
48
|
listInstanceTargets() {
|
|
@@ -259,12 +224,11 @@ export class ConfigService {
|
|
|
259
224
|
if (workflowsPathChanged) {
|
|
260
225
|
this.migrateWorkflowsPath(currentWorkflowsPath, workflowsPathPatch);
|
|
261
226
|
}
|
|
262
|
-
const legacyWorkflowDir = workflowsPathChanged ? undefined : this.resolvePreservedLegacyWorkflowsPath(environment, currentTarget);
|
|
263
227
|
const nextEnvironment = stripUndefined({
|
|
264
228
|
...environment,
|
|
265
229
|
name: nextName,
|
|
266
230
|
workflowsPath: workflowsPathPatch ?? environment.workflowsPath,
|
|
267
|
-
legacyWorkflowDir,
|
|
231
|
+
legacyWorkflowDir: undefined,
|
|
268
232
|
workflowDir: undefined,
|
|
269
233
|
syncFolder: undefined,
|
|
270
234
|
environmentTargetId: target?.id || environment.environmentTargetId,
|
|
@@ -311,8 +275,7 @@ export class ConfigService {
|
|
|
311
275
|
return this.findInstanceTarget(this.ensureV4WorkspaceConfig(), nameOrId);
|
|
312
276
|
}
|
|
313
277
|
resolveEnvironment(environmentNameOrId) {
|
|
314
|
-
const
|
|
315
|
-
const config = persisted.version === 4 ? persisted : this.v3ToV4WorkspaceConfig();
|
|
278
|
+
const config = this.readWorkspaceConfigFile();
|
|
316
279
|
if (config.environments.length === 0) {
|
|
317
280
|
throw new Error('No workspace environment is configured. Run `n8nac env add` first.');
|
|
318
281
|
}
|
|
@@ -322,7 +285,7 @@ export class ConfigService {
|
|
|
322
285
|
? this.findEnvironment(config, config.activeEnvironmentId)
|
|
323
286
|
: config.environments[0];
|
|
324
287
|
const target = this.findInstanceTarget(config, environment.environmentTargetId);
|
|
325
|
-
return this.resolveEnvironmentFromTarget(environment, target, environmentNameOrId ? 'explicit' :
|
|
288
|
+
return this.resolveEnvironmentFromTarget(environment, target, environmentNameOrId ? 'explicit' : 'workspace-default');
|
|
326
289
|
}
|
|
327
290
|
async prepareEnvironment(environmentNameOrId) {
|
|
328
291
|
const resolved = this.resolveEnvironment(environmentNameOrId);
|
|
@@ -331,18 +294,7 @@ export class ConfigService {
|
|
|
331
294
|
const identity = await this.resolveN8nIdentity(resolved.host, resolved.apiKey, undefined, resolved.instanceIdentifier || resolved.environmentTargetId).catch(() => undefined);
|
|
332
295
|
const instanceIdentifier = identity?.instanceIdentifier || resolved.instanceIdentifier;
|
|
333
296
|
const instanceUserIdentifier = identity?.instanceUserIdentifier || resolved.instanceUserIdentifier;
|
|
334
|
-
const workflowsPath = this.resolveEnvironmentWorkflowsPath(resolved.environment
|
|
335
|
-
instanceIdentifier,
|
|
336
|
-
instanceUserIdentifier,
|
|
337
|
-
legacyInstanceIdentifier: resolved.environmentTarget.kind === 'external-instance'
|
|
338
|
-
? resolved.environmentTarget.instanceIdentifier
|
|
339
|
-
: undefined,
|
|
340
|
-
legacyInstanceUserIdentifier: resolved.environmentTarget.kind === 'external-instance'
|
|
341
|
-
? resolved.environmentTarget.instanceUserIdentifier
|
|
342
|
-
: undefined,
|
|
343
|
-
projectId: resolved.projectId,
|
|
344
|
-
projectName: resolved.projectName,
|
|
345
|
-
});
|
|
297
|
+
const workflowsPath = this.resolveEnvironmentWorkflowsPath(resolved.environment);
|
|
346
298
|
return {
|
|
347
299
|
...resolved,
|
|
348
300
|
workflowsPath,
|
|
@@ -378,14 +330,7 @@ export class ConfigService {
|
|
|
378
330
|
instanceIdentifier = identity?.instanceIdentifier || instanceIdentifier;
|
|
379
331
|
instanceUserIdentifier = identity?.instanceUserIdentifier || instanceUserIdentifier;
|
|
380
332
|
}
|
|
381
|
-
const workflowsPath = this.resolveEnvironmentWorkflowsPath(resolved.environment
|
|
382
|
-
instanceIdentifier,
|
|
383
|
-
instanceUserIdentifier,
|
|
384
|
-
legacyInstanceIdentifier: resolved.instance.instanceIdentifier,
|
|
385
|
-
legacyInstanceUserIdentifier: resolved.instance.instanceUserIdentifier,
|
|
386
|
-
projectId,
|
|
387
|
-
projectName,
|
|
388
|
-
});
|
|
333
|
+
const workflowsPath = this.resolveEnvironmentWorkflowsPath(resolved.environment);
|
|
389
334
|
return {
|
|
390
335
|
...resolved,
|
|
391
336
|
host: context.host,
|
|
@@ -408,8 +353,7 @@ export class ConfigService {
|
|
|
408
353
|
return this.listInstances();
|
|
409
354
|
}
|
|
410
355
|
listInstances() {
|
|
411
|
-
|
|
412
|
-
return this.manager.listInstances().map((instance) => this.toInstanceProfile(instance, overrides));
|
|
356
|
+
return this.manager.listInstances().map((instance) => this.toInstanceProfile(instance));
|
|
413
357
|
}
|
|
414
358
|
getInstanceConfig(instanceId) {
|
|
415
359
|
return this.listInstances().find((instance) => instance.id === instanceId);
|
|
@@ -425,55 +369,38 @@ export class ConfigService {
|
|
|
425
369
|
if (effective?.sourceKind === 'managed-instance' && effective.activeInstanceId) {
|
|
426
370
|
return this.getInstanceConfig(effective.activeInstanceId);
|
|
427
371
|
}
|
|
428
|
-
|
|
429
|
-
return legacy ? this.contextToInstanceProfile(legacy) : undefined;
|
|
372
|
+
return undefined;
|
|
430
373
|
}
|
|
431
374
|
getEffectiveInstanceConfig(instanceId) {
|
|
432
375
|
if (instanceId) {
|
|
433
|
-
const
|
|
434
|
-
return
|
|
376
|
+
const instance = this.manager.getInstance(instanceId);
|
|
377
|
+
return instance ? this.toInstanceProfile(instance) : undefined;
|
|
435
378
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
if (environment)
|
|
439
|
-
return this.environmentToInstanceProfile(environment);
|
|
440
|
-
}
|
|
441
|
-
const effective = tryResolve(() => this.resolveWorkspaceContext());
|
|
442
|
-
return effective ? this.contextToInstanceProfile(effective) : undefined;
|
|
379
|
+
const environment = tryResolve(() => this.resolveEnvironment());
|
|
380
|
+
return environment ? this.environmentToInstanceProfile(environment) : undefined;
|
|
443
381
|
}
|
|
444
382
|
getEffectiveContext(instanceId) {
|
|
445
|
-
if (
|
|
446
|
-
|
|
383
|
+
if (instanceId) {
|
|
384
|
+
throw new Error('Explicit instance context is not supported with V4 workspace environments. Resolve a workspace environment instead.');
|
|
447
385
|
}
|
|
448
|
-
return tryResolve(() => this.
|
|
386
|
+
return this.resolvedEnvironmentToEffectiveContext(tryResolve(() => this.resolveEnvironment()));
|
|
449
387
|
}
|
|
450
388
|
async prepareWorkspaceContext(input) {
|
|
451
|
-
const
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
if (environment || (!instanceId && this.isWorkspaceConfigV4())) {
|
|
455
|
-
return this.resolvedEnvironmentToEffectiveContext(await this.prepareEnvironment(environment));
|
|
389
|
+
const environment = typeof input === 'string' ? input : input?.environment;
|
|
390
|
+
if (typeof input === 'object' && input?.instanceId) {
|
|
391
|
+
throw new Error('Explicit instance context is not supported with V4 workspace environments. Resolve a workspace environment instead.');
|
|
456
392
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
instanceId,
|
|
460
|
-
syncFolderDefault: 'workspace',
|
|
461
|
-
consumer,
|
|
462
|
-
autoStart: true,
|
|
463
|
-
});
|
|
464
|
-
if (prepared.runtime.blocked) {
|
|
465
|
-
throw new Error(prepared.runtime.blocked.message);
|
|
393
|
+
if (typeof input === 'object' && input?.consumer && input.consumer !== 'cli') {
|
|
394
|
+
throw new Error(`Unsupported workspace context consumer: ${input.consumer}`);
|
|
466
395
|
}
|
|
467
|
-
return
|
|
468
|
-
...prepared.context,
|
|
469
|
-
};
|
|
396
|
+
return this.resolvedEnvironmentToEffectiveContext(await this.prepareEnvironment(environment));
|
|
470
397
|
}
|
|
471
398
|
getCurrentInstanceConfigId() {
|
|
472
399
|
return this.getActiveInstanceId();
|
|
473
400
|
}
|
|
474
401
|
getActiveInstanceId() {
|
|
475
402
|
const environment = tryResolve(() => this.resolveEnvironment());
|
|
476
|
-
return environment?.activeInstanceId
|
|
403
|
+
return environment?.activeInstanceId;
|
|
477
404
|
}
|
|
478
405
|
getCurrentInstance() {
|
|
479
406
|
return this.getActiveInstance();
|
|
@@ -609,10 +536,6 @@ export class ConfigService {
|
|
|
609
536
|
};
|
|
610
537
|
}
|
|
611
538
|
saveLocalConfig(config, options = {}) {
|
|
612
|
-
const workspaceConfigIsV4 = this.isWorkspaceConfigV4();
|
|
613
|
-
if (workspaceConfigIsV4) {
|
|
614
|
-
this.assertNoLegacyWorkspaceFields(config);
|
|
615
|
-
}
|
|
616
539
|
const current = (options.createNew ? undefined : (options.instanceId ? this.manager.getInstance(options.instanceId) : this.manager.getGlobalActiveInstance()));
|
|
617
540
|
const host = this.resolveStoredBaseUrl(current, config.host);
|
|
618
541
|
const input = {
|
|
@@ -633,11 +556,7 @@ export class ConfigService {
|
|
|
633
556
|
if (options.apiKey) {
|
|
634
557
|
this.manager.saveApiKey(saved.id, options.apiKey);
|
|
635
558
|
}
|
|
636
|
-
|
|
637
|
-
return this.toInstanceProfile(saved);
|
|
638
|
-
}
|
|
639
|
-
this.writeWorkspaceFields(saved.id, config, options.setActive !== false);
|
|
640
|
-
return this.toInstanceProfile(saved, this.manager.readWorkspaceOverrides(this.workspaceRoot));
|
|
559
|
+
return this.toInstanceProfile(saved);
|
|
641
560
|
}
|
|
642
561
|
saveInstanceProfile(profile, options = {}) {
|
|
643
562
|
return this.saveLocalConfig(profile, {
|
|
@@ -796,677 +715,21 @@ export class ConfigService {
|
|
|
796
715
|
getWorkspaceRoot() {
|
|
797
716
|
return this.workspaceRoot;
|
|
798
717
|
}
|
|
799
|
-
detectLegacyWorkspaceConfig() {
|
|
800
|
-
const configPath = this.getInstanceConfigPath();
|
|
801
|
-
const raw = this.readRawWorkspaceConfig(configPath);
|
|
802
|
-
if (!raw || !this.isLegacyWorkspaceConfig(raw)) {
|
|
803
|
-
return undefined;
|
|
804
|
-
}
|
|
805
|
-
const instances = this.readLegacyInstances(raw);
|
|
806
|
-
const requestedActiveInstanceId = asString(raw.activeInstanceId);
|
|
807
|
-
const activeInstance = requestedActiveInstanceId
|
|
808
|
-
? (instances.find((instance) => instance.id === requestedActiveInstanceId) || instances[0])
|
|
809
|
-
: instances[0];
|
|
810
|
-
const activeInstanceId = activeInstance?.id;
|
|
811
|
-
const workspace = stripUndefined({
|
|
812
|
-
syncFolder: asString(raw.syncFolder) || activeInstance?.syncFolder,
|
|
813
|
-
projectId: asString(raw.projectId) || activeInstance?.projectId,
|
|
814
|
-
projectName: asString(raw.projectName) || activeInstance?.projectName,
|
|
815
|
-
workflowsPath: asString(raw.workflowsPath) || activeInstance?.workflowsPath || asString(raw.workflowDir) || activeInstance?.workflowDir,
|
|
816
|
-
workflowDir: asString(raw.workflowDir) || activeInstance?.workflowDir || asString(raw.workflowsPath) || activeInstance?.workflowsPath,
|
|
817
|
-
customNodesPath: asString(raw.customNodesPath) || activeInstance?.customNodesPath,
|
|
818
|
-
folderSync: asBoolean(raw.folderSync) ?? activeInstance?.folderSync,
|
|
819
|
-
});
|
|
820
|
-
const warnings = [
|
|
821
|
-
'Global n8n instances and API keys now live in n8n-manager, not in n8nac-config.json.',
|
|
822
|
-
'n8nac-config.json will keep workspace environments after migration.',
|
|
823
|
-
requestedActiveInstanceId && requestedActiveInstanceId !== activeInstanceId
|
|
824
|
-
? `Legacy active instance "${requestedActiveInstanceId}" was not found; migration will use ${activeInstanceId ? `"${activeInstanceId}"` : 'no pinned instance'} instead.`
|
|
825
|
-
: undefined,
|
|
826
|
-
instances.some((instance) => instance.hasApiKey)
|
|
827
|
-
? 'Embedded API keys found: --write will move them into the local n8n-manager secret store.'
|
|
828
|
-
: 'No externalInstance API keys found: you may need to run n8n-manager auth set after migration.',
|
|
829
|
-
].filter(Boolean);
|
|
830
|
-
return {
|
|
831
|
-
status: 'legacy-detected',
|
|
832
|
-
configPath,
|
|
833
|
-
version: typeof raw.version === 'number' ? raw.version : undefined,
|
|
834
|
-
activeInstanceId,
|
|
835
|
-
instances,
|
|
836
|
-
workspace,
|
|
837
|
-
warnings,
|
|
838
|
-
};
|
|
839
|
-
}
|
|
840
|
-
migrateLegacyWorkspaceConfig(options = {}) {
|
|
841
|
-
const plan = this.detectLegacyWorkspaceConfig();
|
|
842
|
-
const configPath = this.getInstanceConfigPath();
|
|
843
|
-
if (!plan) {
|
|
844
|
-
return { status: 'not-needed', configPath };
|
|
845
|
-
}
|
|
846
|
-
if (!options.write) {
|
|
847
|
-
return { status: 'dry-run', plan };
|
|
848
|
-
}
|
|
849
|
-
const backupPath = this.createLegacyConfigBackup(configPath);
|
|
850
|
-
const rawLegacyConfig = this.readRawWorkspaceConfig(configPath) || {};
|
|
851
|
-
const migratedInstances = [];
|
|
852
|
-
const migratedPairs = [];
|
|
853
|
-
for (const legacyInstance of plan.instances) {
|
|
854
|
-
const apiKey = this.readLegacyApiKey(legacyInstance.id, rawLegacyConfig)
|
|
855
|
-
|| this.readLegacyStoredApiKey(legacyInstance.id, legacyInstance.host);
|
|
856
|
-
const saved = this.saveLocalConfig({
|
|
857
|
-
host: legacyInstance.host,
|
|
858
|
-
syncFolder: legacyInstance.syncFolder || plan.workspace.syncFolder,
|
|
859
|
-
projectId: legacyInstance.projectId || plan.workspace.projectId,
|
|
860
|
-
projectName: legacyInstance.projectName || plan.workspace.projectName,
|
|
861
|
-
instanceIdentifier: legacyInstance.instanceIdentifier,
|
|
862
|
-
customNodesPath: legacyInstance.customNodesPath || plan.workspace.customNodesPath,
|
|
863
|
-
folderSync: legacyInstance.folderSync ?? plan.workspace.folderSync,
|
|
864
|
-
}, {
|
|
865
|
-
instanceId: legacyInstance.id,
|
|
866
|
-
instanceName: legacyInstance.name,
|
|
867
|
-
setActive: legacyInstance.id === plan.activeInstanceId,
|
|
868
|
-
apiKey,
|
|
869
|
-
});
|
|
870
|
-
migratedInstances.push(saved);
|
|
871
|
-
migratedPairs.push({ legacy: legacyInstance, profile: saved });
|
|
872
|
-
}
|
|
873
|
-
if (migratedPairs.length > 0) {
|
|
874
|
-
const usedIds = [];
|
|
875
|
-
const targetNames = new Set();
|
|
876
|
-
const environmentNames = new Set();
|
|
877
|
-
const environmentTargets = [];
|
|
878
|
-
const environments = [];
|
|
879
|
-
for (const { legacy, profile } of migratedPairs) {
|
|
880
|
-
const baseName = profile.name || legacy.name || profile.host || legacy.id;
|
|
881
|
-
const singleInstanceMigration = migratedPairs.length === 1;
|
|
882
|
-
const targetName = this.uniqueDisplayName(baseName, targetNames);
|
|
883
|
-
const environmentName = this.uniqueDisplayName(singleInstanceMigration ? 'Default' : baseName, environmentNames);
|
|
884
|
-
const targetId = this.uniqueWorkspaceId(singleInstanceMigration ? 'default-instance' : `${profile.id || legacy.id || targetName}-instance`, usedIds);
|
|
885
|
-
usedIds.push(targetId);
|
|
886
|
-
const environmentId = this.uniqueWorkspaceId(singleInstanceMigration ? 'default' : profile.id || legacy.id || environmentName, usedIds);
|
|
887
|
-
usedIds.push(environmentId);
|
|
888
|
-
const syncFolder = legacy.syncFolder || plan.workspace.syncFolder || DEFAULT_SYNC_FOLDER;
|
|
889
|
-
const syncSlug = this.createEnvironmentSyncSlug(environmentName);
|
|
890
|
-
const legacyWorkflowDir = legacy.workflowsPath || legacy.workflowDir || plan.workspace.workflowsPath || plan.workspace.workflowDir;
|
|
891
|
-
environmentTargets.push({
|
|
892
|
-
id: targetId,
|
|
893
|
-
name: targetName,
|
|
894
|
-
kind: 'external-instance',
|
|
895
|
-
url: cleanRequired(profile.host || legacy.host, 'Legacy instance URL'),
|
|
896
|
-
instanceIdentifier: profile.instanceIdentifier || legacy.instanceIdentifier,
|
|
897
|
-
verification: legacy.verification,
|
|
898
|
-
});
|
|
899
|
-
environments.push(stripUndefined({
|
|
900
|
-
id: environmentId,
|
|
901
|
-
name: environmentName,
|
|
902
|
-
environmentTargetId: targetId,
|
|
903
|
-
projectId: legacy.projectId || plan.workspace.projectId,
|
|
904
|
-
projectName: legacy.projectName || plan.workspace.projectName,
|
|
905
|
-
workflowsPath: legacyWorkflowDir || path.join(syncFolder, syncSlug),
|
|
906
|
-
syncSlug,
|
|
907
|
-
syncFolder,
|
|
908
|
-
legacyWorkflowDir,
|
|
909
|
-
customNodesPath: legacy.customNodesPath || plan.workspace.customNodesPath,
|
|
910
|
-
folderSync: legacy.folderSync ?? plan.workspace.folderSync,
|
|
911
|
-
}));
|
|
912
|
-
}
|
|
913
|
-
const activePair = migratedPairs.find(({ legacy }) => legacy.id === plan.activeInstanceId) || migratedPairs[0];
|
|
914
|
-
const activeEnvironmentId = environments[migratedPairs.indexOf(activePair)]?.id || environments[0]?.id;
|
|
915
|
-
this.writeWorkspaceConfigV4({
|
|
916
|
-
version: 4,
|
|
917
|
-
activeEnvironmentId,
|
|
918
|
-
environmentTargets,
|
|
919
|
-
environments,
|
|
920
|
-
});
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
this.manager.writeWorkspaceOverrides(this.workspaceRoot, stripUndefined({
|
|
924
|
-
version: 3,
|
|
925
|
-
syncFolder: plan.workspace.syncFolder,
|
|
926
|
-
projectId: plan.workspace.projectId,
|
|
927
|
-
projectName: plan.workspace.projectName,
|
|
928
|
-
customNodesPath: plan.workspace.customNodesPath,
|
|
929
|
-
folderSync: plan.workspace.folderSync,
|
|
930
|
-
}));
|
|
931
|
-
}
|
|
932
|
-
return { status: 'migrated', plan, backupPath, instances: migratedInstances };
|
|
933
|
-
}
|
|
934
|
-
detectWorkspaceMigration() {
|
|
935
|
-
const configPath = this.getInstanceConfigPath();
|
|
936
|
-
const legacyMigration = this.detectLegacyWorkspaceConfig();
|
|
937
|
-
const globalInstancesMigration = this.detectGlobalInstancesMigration();
|
|
938
|
-
if (!legacyMigration && !globalInstancesMigration)
|
|
939
|
-
return undefined;
|
|
940
|
-
return {
|
|
941
|
-
status: 'migration-required',
|
|
942
|
-
configPath,
|
|
943
|
-
legacyMigration,
|
|
944
|
-
globalInstancesMigration,
|
|
945
|
-
warnings: [
|
|
946
|
-
...(legacyMigration?.warnings || []),
|
|
947
|
-
...(globalInstancesMigration?.warnings || []),
|
|
948
|
-
],
|
|
949
|
-
};
|
|
950
|
-
}
|
|
951
|
-
migrateWorkspaceConfiguration(options = {}) {
|
|
952
|
-
const initialPlan = this.detectWorkspaceMigration();
|
|
953
|
-
const configPath = this.getInstanceConfigPath();
|
|
954
|
-
if (!initialPlan)
|
|
955
|
-
return { status: 'not-needed', configPath };
|
|
956
|
-
if (!options.write)
|
|
957
|
-
return { status: 'dry-run', plan: initialPlan };
|
|
958
|
-
const snapshot = this.createWorkspaceMigrationSnapshot();
|
|
959
|
-
try {
|
|
960
|
-
let legacyMigration;
|
|
961
|
-
if (initialPlan.legacyMigration) {
|
|
962
|
-
const legacyResult = this.migrateLegacyWorkspaceConfig({ write: true });
|
|
963
|
-
if (legacyResult.status === 'migrated') {
|
|
964
|
-
legacyMigration = legacyResult;
|
|
965
|
-
this.preserveWorkspaceMigrationApiKeyFallback(options.legacyApiKeyFallback, legacyResult.instances);
|
|
966
|
-
}
|
|
967
|
-
}
|
|
968
|
-
const currentGlobalPlan = this.detectGlobalInstancesMigration();
|
|
969
|
-
let globalInstancesMigration;
|
|
970
|
-
if (currentGlobalPlan) {
|
|
971
|
-
const globalResult = this.migrateGlobalInstancesToEnvironments({ write: true });
|
|
972
|
-
if (globalResult.status === 'migrated') {
|
|
973
|
-
globalInstancesMigration = globalResult;
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
const remainingPlan = this.detectWorkspaceMigration();
|
|
977
|
-
if (remainingPlan) {
|
|
978
|
-
throw new Error(this.formatIncompleteWorkspaceMigrationError(remainingPlan));
|
|
979
|
-
}
|
|
980
|
-
return {
|
|
981
|
-
status: 'migrated',
|
|
982
|
-
plan: initialPlan,
|
|
983
|
-
legacyMigration,
|
|
984
|
-
globalInstancesMigration,
|
|
985
|
-
backupPath: legacyMigration?.backupPath,
|
|
986
|
-
migratedEnvironmentIds: globalInstancesMigration?.migratedEnvironmentIds || [],
|
|
987
|
-
deletedGlobalInstanceIds: globalInstancesMigration?.deletedGlobalInstanceIds || [],
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
catch (error) {
|
|
991
|
-
this.restoreWorkspaceMigrationSnapshot(snapshot);
|
|
992
|
-
throw error;
|
|
993
|
-
}
|
|
994
|
-
}
|
|
995
|
-
toWorkspaceMigrationReport(result) {
|
|
996
|
-
if (result.status === 'not-needed') {
|
|
997
|
-
return {
|
|
998
|
-
status: result.status,
|
|
999
|
-
configPath: result.configPath,
|
|
1000
|
-
required: false,
|
|
1001
|
-
operations: [],
|
|
1002
|
-
warnings: [],
|
|
1003
|
-
};
|
|
1004
|
-
}
|
|
1005
|
-
return {
|
|
1006
|
-
status: result.status,
|
|
1007
|
-
configPath: result.plan.configPath,
|
|
1008
|
-
required: result.status === 'dry-run',
|
|
1009
|
-
operations: this.workspaceMigrationPlanToOperations(result.plan),
|
|
1010
|
-
warnings: result.plan.warnings,
|
|
1011
|
-
nextCommand: result.status === 'dry-run' ? 'n8nac workspace migrate --json' : undefined,
|
|
1012
|
-
applyCommand: result.status === 'dry-run' ? 'n8nac workspace migrate --write' : undefined,
|
|
1013
|
-
backupPath: result.status === 'migrated' ? result.backupPath : undefined,
|
|
1014
|
-
migratedEnvironmentIds: result.status === 'migrated' ? result.migratedEnvironmentIds : undefined,
|
|
1015
|
-
deletedGlobalInstanceIds: result.status === 'migrated' ? result.deletedGlobalInstanceIds : undefined,
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
workspaceMigrationPlanToReport(plan) {
|
|
1019
|
-
return {
|
|
1020
|
-
status: 'dry-run',
|
|
1021
|
-
configPath: plan.configPath,
|
|
1022
|
-
required: true,
|
|
1023
|
-
operations: this.workspaceMigrationPlanToOperations(plan),
|
|
1024
|
-
warnings: plan.warnings,
|
|
1025
|
-
nextCommand: 'n8nac workspace migrate --json',
|
|
1026
|
-
applyCommand: 'n8nac workspace migrate --write',
|
|
1027
|
-
};
|
|
1028
|
-
}
|
|
1029
|
-
workspaceMigrationPlanToOperations(plan) {
|
|
1030
|
-
const operations = [];
|
|
1031
|
-
if (plan.legacyMigration) {
|
|
1032
|
-
operations.push({
|
|
1033
|
-
id: 'legacy-workspace-config',
|
|
1034
|
-
label: 'Legacy workspace config',
|
|
1035
|
-
description: 'Convert legacy n8nac workspace config into workspace environments and local n8n-manager secrets.',
|
|
1036
|
-
instanceCount: plan.legacyMigration.instances.length,
|
|
1037
|
-
instances: plan.legacyMigration.instances.map((instance) => stripUndefined({
|
|
1038
|
-
id: instance.id,
|
|
1039
|
-
name: instance.name,
|
|
1040
|
-
kind: 'legacy-workspace-instance',
|
|
1041
|
-
url: instance.host,
|
|
1042
|
-
projectId: instance.projectId,
|
|
1043
|
-
projectName: instance.projectName,
|
|
1044
|
-
apiKeyAvailable: instance.hasApiKey,
|
|
1045
|
-
})),
|
|
1046
|
-
warnings: plan.legacyMigration.warnings,
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
if (plan.globalInstancesMigration) {
|
|
1050
|
-
operations.push({
|
|
1051
|
-
id: 'global-instances',
|
|
1052
|
-
label: 'Global/v2 instances',
|
|
1053
|
-
description: 'Attach managed instances to this workspace and copy external global instances into workspace environments.',
|
|
1054
|
-
instanceCount: plan.globalInstancesMigration.instances.length,
|
|
1055
|
-
instances: plan.globalInstancesMigration.instances.map((instance) => stripUndefined({
|
|
1056
|
-
id: instance.id,
|
|
1057
|
-
name: instance.name,
|
|
1058
|
-
kind: instance.mode,
|
|
1059
|
-
url: instance.url,
|
|
1060
|
-
projectId: instance.projectId,
|
|
1061
|
-
projectName: instance.projectName,
|
|
1062
|
-
apiKeyAvailable: instance.apiKeyAvailable,
|
|
1063
|
-
})),
|
|
1064
|
-
warnings: plan.globalInstancesMigration.warnings,
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
return operations;
|
|
1068
|
-
}
|
|
1069
|
-
detectGlobalInstancesMigration() {
|
|
1070
|
-
const configPath = this.getInstanceConfigPath();
|
|
1071
|
-
const global = this.manager.getGlobalConfig();
|
|
1072
|
-
const workspace = this.readWorkspaceConfigFileSafe();
|
|
1073
|
-
const environmentTargetIds = new Set(workspace.environments.map((environment) => environment.environmentTargetId));
|
|
1074
|
-
const instances = global.instances
|
|
1075
|
-
.filter((instance) => {
|
|
1076
|
-
if (this.getGlobalExternalInstanceUrl(instance))
|
|
1077
|
-
return true;
|
|
1078
|
-
if (instance.mode !== 'managed-local-docker')
|
|
1079
|
-
return false;
|
|
1080
|
-
const migratedTarget = workspace.environmentTargets.find((target) => {
|
|
1081
|
-
return target.kind === 'managed-instance'
|
|
1082
|
-
&& target.managedInstanceId === instance.id
|
|
1083
|
-
&& environmentTargetIds.has(target.id);
|
|
1084
|
-
});
|
|
1085
|
-
return !migratedTarget
|
|
1086
|
-
&& instance.metadata?.n8nacWorkspaceEnvironmentModel !== 'v4';
|
|
1087
|
-
})
|
|
1088
|
-
.map((instance) => stripUndefined({
|
|
1089
|
-
id: instance.id,
|
|
1090
|
-
name: instance.name || this.getGlobalExternalInstanceUrl(instance) || instance.id,
|
|
1091
|
-
mode: instance.mode === 'managed-local-docker' ? 'managed-instance' : 'external-instance',
|
|
1092
|
-
url: this.getGlobalExternalInstanceUrl(instance) || '',
|
|
1093
|
-
projectId: instance.defaultProject?.id,
|
|
1094
|
-
projectName: instance.defaultProject?.name,
|
|
1095
|
-
apiKeyAvailable: Boolean(this.manager.getApiKey(instance.id)),
|
|
1096
|
-
}));
|
|
1097
|
-
if (!instances.length)
|
|
1098
|
-
return undefined;
|
|
1099
|
-
return {
|
|
1100
|
-
status: 'global-instances-detected',
|
|
1101
|
-
configPath,
|
|
1102
|
-
activeInstanceId: global.activeInstanceId,
|
|
1103
|
-
instances,
|
|
1104
|
-
warnings: [
|
|
1105
|
-
'Global n8n instances belong to the previous v2 workspace model.',
|
|
1106
|
-
'Migration will copy external instances into this workspace as environments, move API keys to workspace target secrets, then remove the old external global instance entries.',
|
|
1107
|
-
'Managed instances will be added to this workspace as environments and will stay global.',
|
|
1108
|
-
],
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
migrateGlobalInstancesToEnvironments(options = {}) {
|
|
1112
|
-
const plan = this.detectGlobalInstancesMigration();
|
|
1113
|
-
const configPath = this.getInstanceConfigPath();
|
|
1114
|
-
if (!plan)
|
|
1115
|
-
return { status: 'not-needed', configPath };
|
|
1116
|
-
if (!options.write)
|
|
1117
|
-
return { status: 'dry-run', plan };
|
|
1118
|
-
const current = this.readWorkspaceConfigFileSafe();
|
|
1119
|
-
const usedIds = [
|
|
1120
|
-
...current.environmentTargets.map((item) => item.id),
|
|
1121
|
-
...current.environments.map((item) => item.id),
|
|
1122
|
-
];
|
|
1123
|
-
const targetNames = new Set(current.environmentTargets.map((item) => item.name));
|
|
1124
|
-
const environmentNames = new Set(current.environments.map((item) => item.name));
|
|
1125
|
-
const environmentTargets = [...current.environmentTargets];
|
|
1126
|
-
const environments = [...current.environments];
|
|
1127
|
-
const migratedEnvironmentIds = [];
|
|
1128
|
-
const deletedGlobalInstanceIds = [];
|
|
1129
|
-
let activeMigratedEnvironmentId;
|
|
1130
|
-
for (const item of plan.instances) {
|
|
1131
|
-
const instance = this.manager.getInstance(item.id);
|
|
1132
|
-
if (!instance)
|
|
1133
|
-
continue;
|
|
1134
|
-
if (instance.mode === 'managed-local-docker') {
|
|
1135
|
-
const existingTarget = environmentTargets.find((target) => target.kind === 'managed-instance' && target.managedInstanceId === instance.id);
|
|
1136
|
-
let targetId = existingTarget?.id;
|
|
1137
|
-
if (!targetId) {
|
|
1138
|
-
const targetName = this.uniqueDisplayName(instance.name || instance.id, targetNames);
|
|
1139
|
-
targetId = this.uniqueWorkspaceId(instance.id, usedIds);
|
|
1140
|
-
usedIds.push(targetId);
|
|
1141
|
-
environmentTargets.push({
|
|
1142
|
-
id: targetId,
|
|
1143
|
-
name: targetName,
|
|
1144
|
-
kind: 'managed-instance',
|
|
1145
|
-
managedInstanceId: instance.id,
|
|
1146
|
-
});
|
|
1147
|
-
}
|
|
1148
|
-
let existingEnvironment = environments.find((environment) => environment.environmentTargetId === targetId);
|
|
1149
|
-
if (!existingEnvironment) {
|
|
1150
|
-
const environmentName = this.uniqueDisplayName(instance.name || instance.id, environmentNames);
|
|
1151
|
-
const environmentId = this.uniqueWorkspaceId(instance.id || environmentName, usedIds);
|
|
1152
|
-
usedIds.push(environmentId);
|
|
1153
|
-
const syncFolder = DEFAULT_SYNC_FOLDER;
|
|
1154
|
-
const syncSlug = this.createEnvironmentSyncSlug(environmentName);
|
|
1155
|
-
existingEnvironment = stripUndefined({
|
|
1156
|
-
id: environmentId,
|
|
1157
|
-
name: environmentName,
|
|
1158
|
-
environmentTargetId: targetId,
|
|
1159
|
-
projectId: instance.defaultProject?.id || 'personal',
|
|
1160
|
-
projectName: instance.defaultProject?.name || 'Personal',
|
|
1161
|
-
workflowsPath: path.join(syncFolder, syncSlug),
|
|
1162
|
-
syncSlug,
|
|
1163
|
-
syncFolder,
|
|
1164
|
-
});
|
|
1165
|
-
environments.push(existingEnvironment);
|
|
1166
|
-
migratedEnvironmentIds.push(environmentId);
|
|
1167
|
-
}
|
|
1168
|
-
if (instance.id === plan.activeInstanceId)
|
|
1169
|
-
activeMigratedEnvironmentId = existingEnvironment.id;
|
|
1170
|
-
this.manager.upsertInstance({
|
|
1171
|
-
id: instance.id,
|
|
1172
|
-
metadata: {
|
|
1173
|
-
...(instance.metadata ?? {}),
|
|
1174
|
-
n8nacWorkspaceEnvironmentModel: 'v4',
|
|
1175
|
-
},
|
|
1176
|
-
}, { setActive: false });
|
|
1177
|
-
continue;
|
|
1178
|
-
}
|
|
1179
|
-
const externalUrl = this.getGlobalExternalInstanceUrl(instance);
|
|
1180
|
-
if (!externalUrl)
|
|
1181
|
-
continue;
|
|
1182
|
-
const apiKey = this.manager.getApiKey(instance.id);
|
|
1183
|
-
const normalizedBaseUrl = this.normalizeHost(externalUrl);
|
|
1184
|
-
const existingTargetIndex = environmentTargets.findIndex((target) => {
|
|
1185
|
-
if (target.kind === 'managed-instance')
|
|
1186
|
-
return target.managedInstanceId === instance.id;
|
|
1187
|
-
return this.normalizeHost(target.url) === normalizedBaseUrl;
|
|
1188
|
-
});
|
|
1189
|
-
if (existingTargetIndex >= 0) {
|
|
1190
|
-
const existingTarget = environmentTargets[existingTargetIndex];
|
|
1191
|
-
if (existingTarget.kind === 'managed-instance') {
|
|
1192
|
-
environmentTargets[existingTargetIndex] = {
|
|
1193
|
-
id: existingTarget.id,
|
|
1194
|
-
name: existingTarget.name,
|
|
1195
|
-
kind: 'external-instance',
|
|
1196
|
-
url: externalUrl,
|
|
1197
|
-
instanceIdentifier: instance.instanceIdentifier,
|
|
1198
|
-
verification: instance.verification,
|
|
1199
|
-
description: existingTarget.description,
|
|
1200
|
-
};
|
|
1201
|
-
}
|
|
1202
|
-
if (apiKey)
|
|
1203
|
-
this.manager.saveApiKey(existingTarget.id, apiKey);
|
|
1204
|
-
let existingEnvironment = environments.find((environment) => environment.environmentTargetId === existingTarget.id);
|
|
1205
|
-
if (!existingEnvironment) {
|
|
1206
|
-
const environmentName = this.uniqueDisplayName(instance.name || externalUrl || instance.id, environmentNames);
|
|
1207
|
-
const environmentId = this.uniqueWorkspaceId(instance.id || environmentName, usedIds);
|
|
1208
|
-
usedIds.push(environmentId);
|
|
1209
|
-
const syncFolder = DEFAULT_SYNC_FOLDER;
|
|
1210
|
-
const syncSlug = this.createEnvironmentSyncSlug(environmentName);
|
|
1211
|
-
existingEnvironment = stripUndefined({
|
|
1212
|
-
id: environmentId,
|
|
1213
|
-
name: environmentName,
|
|
1214
|
-
environmentTargetId: existingTarget.id,
|
|
1215
|
-
projectId: instance.defaultProject?.id || 'personal',
|
|
1216
|
-
projectName: instance.defaultProject?.name || 'Personal',
|
|
1217
|
-
workflowsPath: path.join(syncFolder, syncSlug),
|
|
1218
|
-
syncSlug,
|
|
1219
|
-
syncFolder,
|
|
1220
|
-
});
|
|
1221
|
-
environments.push(existingEnvironment);
|
|
1222
|
-
migratedEnvironmentIds.push(environmentId);
|
|
1223
|
-
}
|
|
1224
|
-
if (instance.id === plan.activeInstanceId)
|
|
1225
|
-
activeMigratedEnvironmentId = existingEnvironment.id;
|
|
1226
|
-
continue;
|
|
1227
|
-
}
|
|
1228
|
-
const targetName = this.uniqueDisplayName(instance.name || externalUrl || instance.id, targetNames);
|
|
1229
|
-
const environmentName = this.uniqueDisplayName(instance.name || externalUrl || instance.id, environmentNames);
|
|
1230
|
-
const targetId = this.uniqueWorkspaceId(`${instance.id}-instance`, usedIds);
|
|
1231
|
-
usedIds.push(targetId);
|
|
1232
|
-
const environmentId = this.uniqueWorkspaceId(instance.id || environmentName, usedIds);
|
|
1233
|
-
usedIds.push(environmentId);
|
|
1234
|
-
const projectId = instance.defaultProject?.id || 'personal';
|
|
1235
|
-
const projectName = instance.defaultProject?.name || 'Personal';
|
|
1236
|
-
const syncFolder = DEFAULT_SYNC_FOLDER;
|
|
1237
|
-
const syncSlug = this.createEnvironmentSyncSlug(environmentName);
|
|
1238
|
-
environmentTargets.push({
|
|
1239
|
-
id: targetId,
|
|
1240
|
-
name: targetName,
|
|
1241
|
-
kind: 'external-instance',
|
|
1242
|
-
url: externalUrl,
|
|
1243
|
-
instanceIdentifier: instance.instanceIdentifier,
|
|
1244
|
-
verification: instance.verification,
|
|
1245
|
-
});
|
|
1246
|
-
environments.push(stripUndefined({
|
|
1247
|
-
id: environmentId,
|
|
1248
|
-
name: environmentName,
|
|
1249
|
-
environmentTargetId: targetId,
|
|
1250
|
-
projectId,
|
|
1251
|
-
projectName,
|
|
1252
|
-
workflowsPath: path.join(syncFolder, syncSlug),
|
|
1253
|
-
syncSlug,
|
|
1254
|
-
syncFolder,
|
|
1255
|
-
}));
|
|
1256
|
-
if (apiKey)
|
|
1257
|
-
this.manager.saveApiKey(targetId, apiKey);
|
|
1258
|
-
migratedEnvironmentIds.push(environmentId);
|
|
1259
|
-
if (instance.id === plan.activeInstanceId)
|
|
1260
|
-
activeMigratedEnvironmentId = environmentId;
|
|
1261
|
-
}
|
|
1262
|
-
const activeEnvironmentId = current.activeEnvironmentId
|
|
1263
|
-
|| activeMigratedEnvironmentId
|
|
1264
|
-
|| migratedEnvironmentIds[0]
|
|
1265
|
-
|| environments[0]?.id;
|
|
1266
|
-
this.writeWorkspaceConfigV4(stripUndefined({
|
|
1267
|
-
version: 4,
|
|
1268
|
-
activeEnvironmentId,
|
|
1269
|
-
environmentTargets,
|
|
1270
|
-
environments,
|
|
1271
|
-
}));
|
|
1272
|
-
for (const item of plan.instances) {
|
|
1273
|
-
const instance = this.manager.getInstance(item.id);
|
|
1274
|
-
if (instance && this.getGlobalExternalInstanceUrl(instance)) {
|
|
1275
|
-
this.manager.deleteInstance(item.id);
|
|
1276
|
-
deletedGlobalInstanceIds.push(item.id);
|
|
1277
|
-
}
|
|
1278
|
-
}
|
|
1279
|
-
return { status: 'migrated', plan, migratedEnvironmentIds, deletedGlobalInstanceIds };
|
|
1280
|
-
}
|
|
1281
718
|
resolveWorkspacePath(targetPath) {
|
|
1282
719
|
return path.isAbsolute(targetPath)
|
|
1283
720
|
? targetPath
|
|
1284
721
|
: path.resolve(this.workspaceRoot, targetPath);
|
|
1285
722
|
}
|
|
1286
|
-
readRawWorkspaceConfig(configPath) {
|
|
1287
|
-
if (!fs.existsSync(configPath)) {
|
|
1288
|
-
return undefined;
|
|
1289
|
-
}
|
|
1290
|
-
try {
|
|
1291
|
-
const value = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1292
|
-
return value && typeof value === 'object' && !Array.isArray(value)
|
|
1293
|
-
? value
|
|
1294
|
-
: undefined;
|
|
1295
|
-
}
|
|
1296
|
-
catch {
|
|
1297
|
-
return undefined;
|
|
1298
|
-
}
|
|
1299
|
-
}
|
|
1300
|
-
isLegacyWorkspaceConfig(raw) {
|
|
1301
|
-
if (raw.version === 1 || raw.version === 2)
|
|
1302
|
-
return true;
|
|
1303
|
-
if (Array.isArray(raw.instances))
|
|
1304
|
-
return true;
|
|
1305
|
-
if (typeof raw.apiKey === 'string')
|
|
1306
|
-
return true;
|
|
1307
|
-
return false;
|
|
1308
|
-
}
|
|
1309
|
-
readLegacyInstances(raw) {
|
|
1310
|
-
const rawInstances = Array.isArray(raw.instances) ? raw.instances : [];
|
|
1311
|
-
if (rawInstances.length > 0) {
|
|
1312
|
-
return rawInstances
|
|
1313
|
-
.map((candidate, index) => this.toLegacyInstance(candidate, raw, index, false))
|
|
1314
|
-
.filter((instance) => Boolean(instance));
|
|
1315
|
-
}
|
|
1316
|
-
const candidates = this.hasRootLegacyInstance(raw) ? [raw] : [];
|
|
1317
|
-
return candidates
|
|
1318
|
-
.map((candidate, index) => this.toLegacyInstance(candidate, raw, index, true))
|
|
1319
|
-
.filter((instance) => Boolean(instance));
|
|
1320
|
-
}
|
|
1321
|
-
hasRootLegacyInstance(raw) {
|
|
1322
|
-
return Boolean(asString(raw.host) || asString(raw.url) || asString(raw.baseUrl));
|
|
1323
|
-
}
|
|
1324
|
-
toLegacyInstance(candidate, root, index, useRootActiveInstanceId) {
|
|
1325
|
-
if (!candidate || typeof candidate !== 'object' || Array.isArray(candidate)) {
|
|
1326
|
-
return undefined;
|
|
1327
|
-
}
|
|
1328
|
-
const value = candidate;
|
|
1329
|
-
const id = asString(value.id) || (useRootActiveInstanceId ? asString(root.activeInstanceId) : undefined) || `legacy-${index + 1}`;
|
|
1330
|
-
const host = asString(value.host) || asString(value.url) || asString(value.baseUrl) || asString(root.host) || asString(root.url) || asString(root.baseUrl);
|
|
1331
|
-
const name = asString(value.name) || host || id;
|
|
1332
|
-
return stripUndefined({
|
|
1333
|
-
id,
|
|
1334
|
-
name,
|
|
1335
|
-
host,
|
|
1336
|
-
syncFolder: asString(value.syncFolder) || asString(root.syncFolder),
|
|
1337
|
-
workflowsPath: asString(value.workflowsPath) || asString(root.workflowsPath),
|
|
1338
|
-
projectId: asString(value.projectId) || asString(root.projectId),
|
|
1339
|
-
projectName: asString(value.projectName) || asString(root.projectName),
|
|
1340
|
-
instanceIdentifier: asString(value.instanceIdentifier) || asString(root.instanceIdentifier),
|
|
1341
|
-
workflowDir: asString(value.workflowDir) || asString(root.workflowDir),
|
|
1342
|
-
verification: value.verification && typeof value.verification === 'object' && !Array.isArray(value.verification)
|
|
1343
|
-
? value.verification
|
|
1344
|
-
: undefined,
|
|
1345
|
-
customNodesPath: asString(value.customNodesPath) || asString(root.customNodesPath),
|
|
1346
|
-
folderSync: asBoolean(value.folderSync) ?? asBoolean(root.folderSync),
|
|
1347
|
-
hasApiKey: Boolean(asString(value.apiKey) || asString(root.apiKey)),
|
|
1348
|
-
});
|
|
1349
|
-
}
|
|
1350
|
-
readLegacyApiKey(instanceId, root) {
|
|
1351
|
-
const instances = Array.isArray(root.instances) ? root.instances : [];
|
|
1352
|
-
const match = instances.find((candidate) => {
|
|
1353
|
-
return candidate && typeof candidate === 'object' && !Array.isArray(candidate)
|
|
1354
|
-
&& asString(candidate.id) === instanceId;
|
|
1355
|
-
});
|
|
1356
|
-
if (match) {
|
|
1357
|
-
return asString(match.apiKey) || asString(root.apiKey);
|
|
1358
|
-
}
|
|
1359
|
-
const syntheticIndex = this.syntheticLegacyIndex(instanceId);
|
|
1360
|
-
const syntheticMatch = syntheticIndex !== undefined ? instances[syntheticIndex] : undefined;
|
|
1361
|
-
if (syntheticMatch && typeof syntheticMatch === 'object' && !Array.isArray(syntheticMatch)) {
|
|
1362
|
-
return asString(syntheticMatch.apiKey) || asString(root.apiKey);
|
|
1363
|
-
}
|
|
1364
|
-
return asString(root.apiKey);
|
|
1365
|
-
}
|
|
1366
|
-
readLegacyStoredApiKey(instanceId, host) {
|
|
1367
|
-
const readFromStore = (store) => {
|
|
1368
|
-
const instanceApiKey = asString(store?.instanceProfiles?.[instanceId]);
|
|
1369
|
-
if (instanceApiKey)
|
|
1370
|
-
return instanceApiKey;
|
|
1371
|
-
if (!host)
|
|
1372
|
-
return undefined;
|
|
1373
|
-
return asString(store?.hosts?.[this.normalizeHost(host)]);
|
|
1374
|
-
};
|
|
1375
|
-
for (const root of [process.env.XDG_CONFIG_HOME, process.env.N8N_MANAGER_HOME]) {
|
|
1376
|
-
if (!root)
|
|
1377
|
-
continue;
|
|
1378
|
-
const filePath = path.join(root, 'n8nac-nodejs', 'credentials.json');
|
|
1379
|
-
if (!fs.existsSync(filePath))
|
|
1380
|
-
continue;
|
|
1381
|
-
try {
|
|
1382
|
-
const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
1383
|
-
const apiKey = readFromStore(value && typeof value === 'object' && !Array.isArray(value) ? value : undefined);
|
|
1384
|
-
if (apiKey)
|
|
1385
|
-
return apiKey;
|
|
1386
|
-
}
|
|
1387
|
-
catch {
|
|
1388
|
-
// Try the Conf-backed store below.
|
|
1389
|
-
}
|
|
1390
|
-
}
|
|
1391
|
-
try {
|
|
1392
|
-
const store = new Conf({
|
|
1393
|
-
projectName: 'n8nac',
|
|
1394
|
-
configName: 'credentials',
|
|
1395
|
-
configFileMode: 0o600,
|
|
1396
|
-
});
|
|
1397
|
-
const instanceProfiles = store.get('instanceProfiles');
|
|
1398
|
-
const hosts = store.get('hosts');
|
|
1399
|
-
return readFromStore({ hosts, instanceProfiles });
|
|
1400
|
-
}
|
|
1401
|
-
catch {
|
|
1402
|
-
return undefined;
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
syntheticLegacyIndex(instanceId) {
|
|
1406
|
-
const match = instanceId.match(/^legacy-(\d+)$/);
|
|
1407
|
-
if (!match)
|
|
1408
|
-
return undefined;
|
|
1409
|
-
const index = Number.parseInt(match[1], 10) - 1;
|
|
1410
|
-
return Number.isSafeInteger(index) && index >= 0 ? index : undefined;
|
|
1411
|
-
}
|
|
1412
|
-
createLegacyConfigBackup(configPath) {
|
|
1413
|
-
const timestamp = new Date().toISOString().replace(/[-:]/g, '').replace(/\..*$/, '').replace('T', '-');
|
|
1414
|
-
const backupPath = path.join(path.dirname(configPath), `n8nac-config.v1-backup-${timestamp}.json`);
|
|
1415
|
-
fs.copyFileSync(configPath, backupPath);
|
|
1416
|
-
return backupPath;
|
|
1417
|
-
}
|
|
1418
|
-
createWorkspaceMigrationSnapshot() {
|
|
1419
|
-
const managerPaths = this.manager;
|
|
1420
|
-
const paths = [
|
|
1421
|
-
this.getInstanceConfigPath(),
|
|
1422
|
-
managerPaths.instancesPath,
|
|
1423
|
-
managerPaths.secretsPath,
|
|
1424
|
-
].filter((value) => Boolean(value));
|
|
1425
|
-
return paths.map((filePath) => ({
|
|
1426
|
-
path: filePath,
|
|
1427
|
-
content: fs.existsSync(filePath) ? fs.readFileSync(filePath) : undefined,
|
|
1428
|
-
}));
|
|
1429
|
-
}
|
|
1430
|
-
restoreWorkspaceMigrationSnapshot(snapshot) {
|
|
1431
|
-
for (const entry of snapshot) {
|
|
1432
|
-
if (entry.content === undefined) {
|
|
1433
|
-
fs.rmSync(entry.path, { force: true });
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
fs.mkdirSync(path.dirname(entry.path), { recursive: true });
|
|
1437
|
-
fs.writeFileSync(entry.path, entry.content);
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
formatIncompleteWorkspaceMigrationError(plan) {
|
|
1441
|
-
const legacyCount = plan.legacyMigration?.instances.length || 0;
|
|
1442
|
-
const globalCount = plan.globalInstancesMigration?.instances.length || 0;
|
|
1443
|
-
return [
|
|
1444
|
-
'Workspace migration did not complete atomically; all migration file changes were rolled back.',
|
|
1445
|
-
`Remaining legacy migration items: ${legacyCount}`,
|
|
1446
|
-
`Remaining global/v2 migration items: ${globalCount}`,
|
|
1447
|
-
'Run `n8nac workspace migrate --json` to inspect the remaining plan before retrying.',
|
|
1448
|
-
].join(' ');
|
|
1449
|
-
}
|
|
1450
723
|
readWorkspaceConfigFile() {
|
|
1451
724
|
const configPath = this.getInstanceConfigPath();
|
|
1452
725
|
if (!fs.existsSync(configPath))
|
|
1453
|
-
return { version:
|
|
726
|
+
return { version: 4, environmentTargets: [], environments: [] };
|
|
1454
727
|
const raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1455
728
|
if (raw?.version === 4) {
|
|
1456
729
|
return this.sanitizeV4Config(raw);
|
|
1457
730
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
}
|
|
1461
|
-
return { version: 3 };
|
|
1462
|
-
}
|
|
1463
|
-
readWorkspaceConfigFileSafe() {
|
|
1464
|
-
try {
|
|
1465
|
-
return this.ensureV4WorkspaceConfig();
|
|
1466
|
-
}
|
|
1467
|
-
catch {
|
|
1468
|
-
return { version: 4, environmentTargets: [], environments: [] };
|
|
1469
|
-
}
|
|
731
|
+
const version = raw?.version === undefined ? 'missing' : String(raw.version);
|
|
732
|
+
throw new Error(`Unsupported n8nac workspace config version: ${version}. Recreate a V4 workspace environment with \`n8nac env add <name> --base-url <url> --workflows-path workflows/<name>\`.`);
|
|
1470
733
|
}
|
|
1471
734
|
isWorkspaceConfigV4() {
|
|
1472
735
|
const configPath = this.getInstanceConfigPath();
|
|
@@ -1476,13 +739,10 @@ export class ConfigService {
|
|
|
1476
739
|
return raw?.version === 4;
|
|
1477
740
|
}
|
|
1478
741
|
assertLegacyWorkspaceOverridesWritable() {
|
|
1479
|
-
|
|
1480
|
-
throw new Error('This workspace uses v4 environments. Use `n8nac instance-target ...` and `n8nac env ...` instead of legacy workspace singleton commands.');
|
|
1481
|
-
}
|
|
742
|
+
throw new Error('Legacy workspace singleton commands are not supported. Use `n8nac env ...` with V4 workspace environments.');
|
|
1482
743
|
}
|
|
1483
744
|
ensureV4WorkspaceConfig() {
|
|
1484
|
-
|
|
1485
|
-
return config.version === 4 ? config : this.v3ToV4WorkspaceConfig();
|
|
745
|
+
return this.readWorkspaceConfigFile();
|
|
1486
746
|
}
|
|
1487
747
|
sanitizeV4Config(raw) {
|
|
1488
748
|
const rawTargets = Array.isArray(raw.environmentTargets) ? raw.environmentTargets : raw.instanceTargets;
|
|
@@ -1623,41 +883,6 @@ export class ConfigService {
|
|
|
1623
883
|
description: cleanOptional(environment.description),
|
|
1624
884
|
});
|
|
1625
885
|
}
|
|
1626
|
-
v3ToV4WorkspaceConfig() {
|
|
1627
|
-
const overrides = tryResolve(() => this.manager.readWorkspaceOverrides(this.workspaceRoot)) || { version: 3 };
|
|
1628
|
-
const hasWorkspaceOverrides = Boolean(overrides.activeInstanceId
|
|
1629
|
-
|| overrides.syncFolder
|
|
1630
|
-
|| overrides.projectId
|
|
1631
|
-
|| overrides.projectName
|
|
1632
|
-
|| overrides.folderSync !== undefined
|
|
1633
|
-
|| overrides.customNodesPath);
|
|
1634
|
-
const managedInstanceId = hasWorkspaceOverrides ? (overrides.activeInstanceId || this.manager.getGlobalConfig().activeInstanceId) : undefined;
|
|
1635
|
-
const environmentTargets = managedInstanceId
|
|
1636
|
-
? [{ id: 'default-instance', name: 'Default Instance', kind: 'managed-instance', managedInstanceId }]
|
|
1637
|
-
: [];
|
|
1638
|
-
const legacyWorkflowDir = overrides.workflowDir;
|
|
1639
|
-
const environments = managedInstanceId
|
|
1640
|
-
? [stripUndefined({
|
|
1641
|
-
id: 'default',
|
|
1642
|
-
name: 'Default',
|
|
1643
|
-
syncSlug: this.createEnvironmentSyncSlug('Default'),
|
|
1644
|
-
environmentTargetId: 'default-instance',
|
|
1645
|
-
projectId: overrides.projectId,
|
|
1646
|
-
projectName: overrides.projectName,
|
|
1647
|
-
workflowsPath: legacyWorkflowDir || path.join(overrides.syncFolder || DEFAULT_SYNC_FOLDER, this.createEnvironmentSyncSlug('Default')),
|
|
1648
|
-
syncFolder: overrides.syncFolder || DEFAULT_SYNC_FOLDER,
|
|
1649
|
-
legacyWorkflowDir,
|
|
1650
|
-
folderSync: overrides.folderSync,
|
|
1651
|
-
customNodesPath: overrides.customNodesPath,
|
|
1652
|
-
})]
|
|
1653
|
-
: [];
|
|
1654
|
-
return {
|
|
1655
|
-
version: 4,
|
|
1656
|
-
activeEnvironmentId: environments[0]?.id,
|
|
1657
|
-
environmentTargets,
|
|
1658
|
-
environments,
|
|
1659
|
-
};
|
|
1660
|
-
}
|
|
1661
886
|
writeWorkspaceConfigV4(config) {
|
|
1662
887
|
const configPath = this.getInstanceConfigPath();
|
|
1663
888
|
const sanitized = this.sanitizeV4Config({ ...config, version: 4 });
|
|
@@ -1722,14 +947,7 @@ export class ConfigService {
|
|
|
1722
947
|
const projectId = environment.projectId || instance.defaultProject?.id;
|
|
1723
948
|
const projectName = environment.projectName || instance.defaultProject?.name;
|
|
1724
949
|
const identity = this.resolveManagedEnvironmentIdentity(instance, host, apiKey);
|
|
1725
|
-
const workflowsPath = this.resolveEnvironmentWorkflowsPath(environment
|
|
1726
|
-
instanceIdentifier: identity.instanceIdentifier,
|
|
1727
|
-
instanceUserIdentifier: identity.instanceUserIdentifier,
|
|
1728
|
-
legacyInstanceIdentifier: instance.instanceIdentifier,
|
|
1729
|
-
legacyInstanceUserIdentifier: instance.instanceUserIdentifier,
|
|
1730
|
-
projectId,
|
|
1731
|
-
projectName,
|
|
1732
|
-
});
|
|
950
|
+
const workflowsPath = this.resolveEnvironmentWorkflowsPath(environment);
|
|
1733
951
|
const syncFolder = workflowsPath;
|
|
1734
952
|
return {
|
|
1735
953
|
environment,
|
|
@@ -1771,14 +989,7 @@ export class ConfigService {
|
|
|
1771
989
|
const globalApiKey = this.getApiKey(host);
|
|
1772
990
|
const apiKey = envApiKey || workspaceApiKey || globalApiKey;
|
|
1773
991
|
const identity = this.resolveExternalEnvironmentIdentity(target, apiKey);
|
|
1774
|
-
const workflowsPath = this.resolveEnvironmentWorkflowsPath(environment
|
|
1775
|
-
instanceIdentifier: identity.instanceIdentifier,
|
|
1776
|
-
instanceUserIdentifier: identity.instanceUserIdentifier,
|
|
1777
|
-
legacyInstanceIdentifier: target.instanceIdentifier,
|
|
1778
|
-
legacyInstanceUserIdentifier: target.instanceUserIdentifier,
|
|
1779
|
-
projectId: environment.projectId,
|
|
1780
|
-
projectName: environment.projectName,
|
|
1781
|
-
});
|
|
992
|
+
const workflowsPath = this.resolveEnvironmentWorkflowsPath(environment);
|
|
1782
993
|
const syncFolder = workflowsPath;
|
|
1783
994
|
return {
|
|
1784
995
|
environment,
|
|
@@ -2125,18 +1336,6 @@ export class ConfigService {
|
|
|
2125
1336
|
instanceSeed,
|
|
2126
1337
|
});
|
|
2127
1338
|
}
|
|
2128
|
-
writeWorkspaceFields(instanceId, config, setActive) {
|
|
2129
|
-
const current = tryResolve(() => this.manager.readWorkspaceOverrides(this.workspaceRoot)) || { version: 3 };
|
|
2130
|
-
this.manager.writeWorkspaceOverrides(this.workspaceRoot, {
|
|
2131
|
-
...current,
|
|
2132
|
-
activeInstanceId: setActive ? instanceId : current.activeInstanceId,
|
|
2133
|
-
syncFolder: config.syncFolder || current.syncFolder,
|
|
2134
|
-
projectId: config.projectId || current.projectId,
|
|
2135
|
-
projectName: config.projectName || current.projectName,
|
|
2136
|
-
folderSync: config.folderSync ?? current.folderSync,
|
|
2137
|
-
customNodesPath: config.customNodesPath || current.customNodesPath,
|
|
2138
|
-
});
|
|
2139
|
-
}
|
|
2140
1339
|
assertNoLegacyWorkspaceFields(config) {
|
|
2141
1340
|
const fields = [
|
|
2142
1341
|
config.syncFolder ? 'syncFolder' : undefined,
|
|
@@ -2149,16 +1348,6 @@ export class ConfigService {
|
|
|
2149
1348
|
throw new Error(`This workspace uses v4 environments. Configure ${fields.join(', ')} with \`n8nac env ...\` instead of legacy workspace fields.`);
|
|
2150
1349
|
}
|
|
2151
1350
|
}
|
|
2152
|
-
resolveWorkspaceContext(instanceId) {
|
|
2153
|
-
const context = this.manager.resolveEffectiveContext({
|
|
2154
|
-
workspaceRoot: this.workspaceRoot,
|
|
2155
|
-
instanceId,
|
|
2156
|
-
syncFolderDefault: 'workspace',
|
|
2157
|
-
});
|
|
2158
|
-
return {
|
|
2159
|
-
...context,
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
1351
|
toInstanceProfile(instance, overrides) {
|
|
2163
1352
|
return {
|
|
2164
1353
|
id: instance.id,
|
|
@@ -2175,47 +1364,6 @@ export class ConfigService {
|
|
|
2175
1364
|
verification: instance.verification,
|
|
2176
1365
|
};
|
|
2177
1366
|
}
|
|
2178
|
-
contextToInstanceProfile(context) {
|
|
2179
|
-
const instanceIdentifier = context.instanceIdentifier;
|
|
2180
|
-
const instanceUserIdentifier = this.canonicalInstanceUserIdentifier(context.instanceUserIdentifier)
|
|
2181
|
-
|| this.readStoredInstanceUserIdentifier(context.instanceIdentifier);
|
|
2182
|
-
return {
|
|
2183
|
-
...this.toInstanceProfile(context.instance),
|
|
2184
|
-
host: context.host,
|
|
2185
|
-
workflowsPath: context.workflowsPath
|
|
2186
|
-
|| context.workflowDir,
|
|
2187
|
-
syncFolder: context.syncFolder,
|
|
2188
|
-
projectId: context.projectId,
|
|
2189
|
-
projectName: context.projectName,
|
|
2190
|
-
instanceIdentifier,
|
|
2191
|
-
instanceUserIdentifier,
|
|
2192
|
-
workflowDir: context.workflowsPath
|
|
2193
|
-
|| context.workflowDir
|
|
2194
|
-
|| this.buildWorkflowDir(context.syncFolder, instanceIdentifier, context.projectName),
|
|
2195
|
-
customNodesPath: context.customNodesPath,
|
|
2196
|
-
folderSync: context.folderSync,
|
|
2197
|
-
};
|
|
2198
|
-
}
|
|
2199
|
-
contextToLocalConfig(context) {
|
|
2200
|
-
return this.toLocalConfig(this.contextToInstanceProfile(context));
|
|
2201
|
-
}
|
|
2202
|
-
toLocalConfig(profile) {
|
|
2203
|
-
if (!profile)
|
|
2204
|
-
return {};
|
|
2205
|
-
const workflowDir = profile.workflowsPath || profile.workflowDir || this.buildWorkflowDir(profile.syncFolder, profile.instanceIdentifier, profile.projectName);
|
|
2206
|
-
return stripUndefined({
|
|
2207
|
-
host: profile.host,
|
|
2208
|
-
workflowsPath: profile.workflowsPath,
|
|
2209
|
-
syncFolder: profile.syncFolder,
|
|
2210
|
-
projectId: profile.projectId,
|
|
2211
|
-
projectName: profile.projectName,
|
|
2212
|
-
instanceIdentifier: profile.instanceIdentifier,
|
|
2213
|
-
instanceUserIdentifier: profile.instanceUserIdentifier,
|
|
2214
|
-
workflowDir,
|
|
2215
|
-
customNodesPath: profile.customNodesPath,
|
|
2216
|
-
folderSync: profile.folderSync,
|
|
2217
|
-
});
|
|
2218
|
-
}
|
|
2219
1367
|
async verifyConnection(host, apiKey, client) {
|
|
2220
1368
|
try {
|
|
2221
1369
|
const resolvedClient = client ?? new N8nApiClient({ host, apiKey });
|
|
@@ -2249,34 +1397,6 @@ export class ConfigService {
|
|
|
2249
1397
|
return host.replace(/\/$/, '');
|
|
2250
1398
|
}
|
|
2251
1399
|
}
|
|
2252
|
-
getGlobalExternalInstanceUrl(instance) {
|
|
2253
|
-
if (instance.mode === 'managed-local-docker' || instance.mode === 'generation-only')
|
|
2254
|
-
return undefined;
|
|
2255
|
-
const mode = String(instance.mode);
|
|
2256
|
-
if (mode !== 'existing' && mode !== 'external-instance')
|
|
2257
|
-
return undefined;
|
|
2258
|
-
const compatibilityUrl = instance.url;
|
|
2259
|
-
return cleanOptional(instance.baseUrl) || cleanOptional(compatibilityUrl);
|
|
2260
|
-
}
|
|
2261
|
-
preserveWorkspaceMigrationApiKeyFallback(fallback, migratedInstances) {
|
|
2262
|
-
const apiKey = fallback?.apiKey?.trim();
|
|
2263
|
-
if (!apiKey)
|
|
2264
|
-
return;
|
|
2265
|
-
const environment = this.resolveEnvironment();
|
|
2266
|
-
const environmentHost = this.normalizeHost(environment.host || '');
|
|
2267
|
-
if (!environmentHost)
|
|
2268
|
-
return;
|
|
2269
|
-
if (fallback?.host && this.normalizeHost(fallback.host) !== environmentHost)
|
|
2270
|
-
return;
|
|
2271
|
-
const migratedInstance = migratedInstances.find((instance) => this.normalizeHost(instance.host || '') === environmentHost);
|
|
2272
|
-
this.saveLocalConfig({ host: environmentHost }, {
|
|
2273
|
-
instanceId: migratedInstance?.id,
|
|
2274
|
-
instanceName: environment.activeInstanceName || environment.environmentTargetName,
|
|
2275
|
-
createNew: !migratedInstance?.id,
|
|
2276
|
-
setActive: false,
|
|
2277
|
-
apiKey,
|
|
2278
|
-
});
|
|
2279
|
-
}
|
|
2280
1400
|
resolveStoredBaseUrl(current, requestedHost) {
|
|
2281
1401
|
const host = requestedHost || current?.baseUrl;
|
|
2282
1402
|
if (current?.baseUrl
|
|
@@ -2287,11 +1407,6 @@ export class ConfigService {
|
|
|
2287
1407
|
}
|
|
2288
1408
|
return host;
|
|
2289
1409
|
}
|
|
2290
|
-
buildWorkflowDir(syncFolder, instanceIdentifier, projectName) {
|
|
2291
|
-
return syncFolder && instanceIdentifier && projectName
|
|
2292
|
-
? path.join(syncFolder, instanceIdentifier, createProjectSlug(projectName))
|
|
2293
|
-
: undefined;
|
|
2294
|
-
}
|
|
2295
1410
|
resolveInputWorkflowsPath(input, environments, name) {
|
|
2296
1411
|
const explicit = cleanOptional(input.workflowsPath) || cleanOptional(input.workflowDir);
|
|
2297
1412
|
if (explicit)
|
|
@@ -2315,37 +1430,8 @@ export class ConfigService {
|
|
|
2315
1430
|
syncSlug: environment.syncSlug,
|
|
2316
1431
|
}, [], environment.name);
|
|
2317
1432
|
}
|
|
2318
|
-
resolveEnvironmentWorkflowsPath(environment
|
|
2319
|
-
|
|
2320
|
-
if (fs.existsSync(configured))
|
|
2321
|
-
return configured;
|
|
2322
|
-
const configuredLegacyDir = environment.legacyWorkflowDir
|
|
2323
|
-
? this.resolveWorkspacePath(environment.legacyWorkflowDir)
|
|
2324
|
-
: undefined;
|
|
2325
|
-
if (configuredLegacyDir && fs.existsSync(configuredLegacyDir))
|
|
2326
|
-
return configuredLegacyDir;
|
|
2327
|
-
const legacy = this.getLegacyEnvironmentWorkflowDirs({
|
|
2328
|
-
syncFolder: environment.syncFolder ? this.resolveWorkspacePath(environment.syncFolder) : undefined,
|
|
2329
|
-
environmentId: environment.id,
|
|
2330
|
-
instanceIdentifier: identity.instanceIdentifier,
|
|
2331
|
-
instanceUserIdentifier: identity.instanceUserIdentifier,
|
|
2332
|
-
legacyInstanceIdentifier: identity.legacyInstanceIdentifier,
|
|
2333
|
-
legacyInstanceUserIdentifier: identity.legacyInstanceUserIdentifier,
|
|
2334
|
-
projectId: identity.projectId,
|
|
2335
|
-
projectName: identity.projectName,
|
|
2336
|
-
}).find((directory) => fs.existsSync(directory));
|
|
2337
|
-
return legacy || configured;
|
|
2338
|
-
}
|
|
2339
|
-
resolvePreservedLegacyWorkflowsPath(environment, target) {
|
|
2340
|
-
const configured = this.resolveWorkspacePath(cleanRequired(environment.workflowsPath, 'Workflows path'));
|
|
2341
|
-
const resolved = this.resolveEnvironmentFromTarget(environment, target, 'explicit');
|
|
2342
|
-
const workflowsPath = resolved.workflowsPath;
|
|
2343
|
-
if (!workflowsPath || workflowsPath === configured || !fs.existsSync(workflowsPath)) {
|
|
2344
|
-
return environment.legacyWorkflowDir;
|
|
2345
|
-
}
|
|
2346
|
-
return path.isAbsolute(workflowsPath)
|
|
2347
|
-
? path.relative(this.workspaceRoot, workflowsPath)
|
|
2348
|
-
: workflowsPath;
|
|
1433
|
+
resolveEnvironmentWorkflowsPath(environment) {
|
|
1434
|
+
return this.resolveWorkspacePath(cleanRequired(environment.workflowsPath, 'Workflows path'));
|
|
2349
1435
|
}
|
|
2350
1436
|
migrateWorkflowsPath(previousPath, nextPath) {
|
|
2351
1437
|
const previous = this.resolveWorkspacePath(previousPath);
|
|
@@ -2381,31 +1467,6 @@ export class ConfigService {
|
|
|
2381
1467
|
return false;
|
|
2382
1468
|
}
|
|
2383
1469
|
}
|
|
2384
|
-
getLegacyEnvironmentWorkflowDirs(input) {
|
|
2385
|
-
const dirs = [];
|
|
2386
|
-
if (!input.syncFolder)
|
|
2387
|
-
return dirs;
|
|
2388
|
-
if (input.environmentId && input.instanceIdentifier && input.instanceUserIdentifier && input.projectId) {
|
|
2389
|
-
dirs.push(path.join(input.syncFolder, createWorkflowDirNameV1({
|
|
2390
|
-
environmentId: input.environmentId,
|
|
2391
|
-
instanceIdentifier: input.instanceIdentifier,
|
|
2392
|
-
instanceUserIdentifier: input.instanceUserIdentifier,
|
|
2393
|
-
projectId: input.projectId,
|
|
2394
|
-
})));
|
|
2395
|
-
}
|
|
2396
|
-
if (input.environmentId && input.legacyInstanceIdentifier && input.legacyInstanceUserIdentifier && input.projectId) {
|
|
2397
|
-
dirs.push(path.join(input.syncFolder, createWorkflowDirNameV1({
|
|
2398
|
-
environmentId: input.environmentId,
|
|
2399
|
-
instanceIdentifier: input.legacyInstanceIdentifier,
|
|
2400
|
-
instanceUserIdentifier: input.legacyInstanceUserIdentifier,
|
|
2401
|
-
projectId: input.projectId,
|
|
2402
|
-
})));
|
|
2403
|
-
}
|
|
2404
|
-
if (input.instanceIdentifier && input.projectName) {
|
|
2405
|
-
dirs.push(path.join(input.syncFolder, input.instanceIdentifier, createProjectSlug(input.projectName)));
|
|
2406
|
-
}
|
|
2407
|
-
return [...new Set(dirs)];
|
|
2408
|
-
}
|
|
2409
1470
|
findConfigRoot(startDir) {
|
|
2410
1471
|
let currentDir = path.resolve(startDir);
|
|
2411
1472
|
while (true) {
|