-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathhandler.ts
More file actions
162 lines (140 loc) · 5.01 KB
/
handler.ts
File metadata and controls
162 lines (140 loc) · 5.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import { Logger } from '../util/logger';
import { MetadataOpts } from './command';
import loadState from '../util/load-state';
import * as cache from './cache';
import { getModuleEntryPoint, registerEsmHook } from '@openfn/runtime';
import { ExecutionPlan } from '@openfn/lexicon';
import { install, removePackage } from '../repo/handler';
// Add created date stamp to the metadata object
const decorateMetadata = (metadata: cache.AdaptorMetadata): void => {
(metadata as cache.CachedMetadata).created = new Date().toISOString();
};
export const getAdaptorPath = async (
adaptor: string,
logger: Logger,
repoDir?: string
): Promise<string | undefined> => {
let adaptorPath;
let adaptorSpecifier;
if (adaptor.match('=')) {
const parts = adaptor.split('=');
adaptorSpecifier = parts[0];
adaptorPath = parts[1];
} else {
// If we've been given a full path, just return it
if (adaptor.endsWith('.js')) {
return adaptor;
}
adaptorSpecifier = adaptor;
// Check if we've been given a partial path (a path to a module)
if (adaptor.startsWith('/')) {
adaptorPath = adaptor;
}
}
if (!adaptorPath || !adaptorPath.endsWith('js')) {
const entry = await getModuleEntryPoint(
adaptorSpecifier,
adaptorPath,
repoDir,
logger
);
adaptorPath = entry?.path;
}
logger.debug('loading adaptor from', adaptorPath);
return adaptorPath;
};
export const shouldAutoinstall = (adaptor: string): boolean =>
adaptor?.length > 0 && !adaptor.startsWith('/') && !adaptor.includes('=');
const metadataHandler = async (
options: MetadataOpts,
logger: Logger
): Promise<void> => {
const { repoDir, adaptors, keepUnsupported } = options;
let adaptor = adaptors[0];
// Check cache first to avoid unnecessary downloads
if (await cache.isAdaptorUnsupported(adaptor, repoDir)) {
logger.info(
`Adaptor ${adaptor} is known to not support metadata (cached) - skipping lookup`
);
logger.error('No metadata helper found');
process.exit(1);
}
// This is needed to import older packages which aren't fully ESM compatible
registerEsmHook();
const state = await loadState({} as ExecutionPlan, options, logger);
logger.success(`Generating metadata`);
// Note that the config will be sanitised, so logging it may not be terrible helpful
logger.info('config:', state);
const config: cache.AdaptorConfiguration = state.configuration;
if (!config || Object.keys(config).length === 0) {
logger.error('ERROR: Invalid configuration passed');
process.exit(1);
}
const finish = (): void => {
logger.success('Done!');
logger.print(cache.getCachePath(repoDir, id));
};
const id = cache.generateKey(config, adaptor);
if (!options.force) {
// generate a hash for the config and check state
logger.debug('config hash: ', id);
const cached = await cache.get<cache.CachedMetadata>(repoDir, id);
if (cached) {
logger.success('Returning metadata from cache');
return finish();
}
}
let wasAutoInstalled = false;
try {
if (shouldAutoinstall(adaptor)) {
const autoinstallResult = await install(
{ packages: [adaptor], repoDir },
logger
);
wasAutoInstalled = true;
// If we've autoinstalled, we may need to set the version number
// TODO we should probably resolve @latest before we do the install actually
adaptor = autoinstallResult[0];
}
const adaptorPath = await getAdaptorPath(adaptor, logger, options.repoDir);
if (!adaptorPath) {
throw new Error(`Could not resolve adaptor path for ${adaptor}`);
}
const mod = await import(adaptorPath);
// Does it export a metadata function?
if (mod.metadata && typeof mod.metadata === 'function') {
logger.info('Metadata function found. Generating metadata...');
const result: cache.AdaptorMetadata = await mod.metadata(config);
decorateMetadata(result);
await cache.set<cache.CachedMetadata>(
repoDir,
id,
result as cache.CachedMetadata
);
finish();
} else {
logger.error('No metadata helper found');
// If we auto-installed this adaptor and user didn't opt out, remove it and cache the result
if (wasAutoInstalled && !keepUnsupported) {
logger.info('Removing unsupported adaptor from disk...');
await removePackage(adaptor, repoDir, logger);
await cache.markAdaptorAsUnsupported(adaptor, repoDir);
logger.info('Adaptor removed and marked as unsupported');
} else if (wasAutoInstalled && keepUnsupported) {
if (adaptor === '@openfn/language-openfn') {
logger.log({ wasAutoInstalled, keepUnsupported });
}
logger.info(
'Keeping unsupported adaptor as requested by --keep-unsupported flag'
);
await cache.markAdaptorAsUnsupported(adaptor, repoDir);
}
process.exit(1);
}
} catch (e) {
logger.error('Exception while generating metadata');
logger.error(e);
process.exit(1);
}
};
export default metadataHandler;