-
Notifications
You must be signed in to change notification settings - Fork 23
/
Copy pathuseCwdProject.ts
231 lines (180 loc) · 5.44 KB
/
useCwdProject.ts
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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import { access, readFile } from 'node:fs/promises';
import { basename, dirname, join, resolve } from 'node:path';
import process from 'node:process';
import { ok, type Result } from '@sapphire/result';
import { useJavaScriptRuntime } from './runtimes/javascript.js';
import { usePythonRuntime } from './runtimes/python.js';
import { ScrapyProjectAnalyzer } from '../projects/scrapy/ScrapyProjectAnalyzer.js';
import { cliDebugPrint } from '../utils/cliDebugPrint.js';
export enum ProjectLanguage {
JavaScript = 0,
Python = 1,
// Special handling for Scrapy projects
Scrapy = 2,
Unknown = 3,
// TODO: eventually when we support entrypoint config in actor json
// https://github.com/apify/apify-cli/issues/766
StaticEntrypoint = 4,
}
export interface Runtime {
executablePath: string;
version: string;
pmName?: string;
pmPath?: string | null;
pmVersion?: string | null;
runtimeShorthand?: string;
}
export interface Entrypoint {
path?: string;
script?: string;
}
export interface CwdProject {
type: ProjectLanguage;
entrypoint?: Entrypoint;
runtime?: Runtime;
}
export interface CwdProjectError {
message: string;
}
export const cwdCache = new Map<string, CwdProject>();
export async function useCwdProject({
cwd = process.cwd(),
}: { cwd?: string } = {}): Promise<Result<CwdProject, CwdProjectError>> {
const cached = cwdCache.get(cwd);
if (cached) {
cliDebugPrint('useCwdProject', { cacheHit: true, project: cached });
return ok(cached);
}
const project: CwdProject = {
type: ProjectLanguage.Unknown,
};
const check = async (): Promise<Result<CwdProject, CwdProjectError> | undefined> => {
const isScrapy = await checkScrapyProject(cwd);
if (isScrapy) {
project.type = ProjectLanguage.Scrapy;
const runtime = await usePythonRuntime({ cwd });
project.runtime = runtime.unwrapOr(undefined);
const scrapyProject = new ScrapyProjectAnalyzer(cwd);
scrapyProject.loadScrapyCfg();
if (scrapyProject.configuration.hasKey('apify', 'mainpy_location')) {
project.entrypoint = {
path: scrapyProject.configuration.get('apify', 'mainpy_location')!,
};
}
return;
}
const isPython = await checkPythonProject(cwd);
if (isPython) {
project.type = ProjectLanguage.Python;
const runtime = await usePythonRuntime({ cwd });
project.entrypoint = {
path: isPython,
};
project.runtime = runtime.unwrapOr(undefined);
return;
}
const isNode = await checkNodeProject(cwd);
if (isNode) {
project.type = ProjectLanguage.JavaScript;
const runtime = await useJavaScriptRuntime();
project.runtime = runtime.unwrapOr(undefined);
if (isNode.type === 'file') {
project.entrypoint = {
path: isNode.path,
};
} else if (isNode.type === 'script') {
project.entrypoint = {
script: isNode.script,
};
}
return;
}
return ok(project);
};
const maybeErr = await check();
if (maybeErr?.isErr()) {
cliDebugPrint('useCwdProject', { cacheHit: false, error: maybeErr });
return maybeErr;
}
cliDebugPrint('useCwdProject', { cacheHit: false, project });
cwdCache.set(cwd, project);
return ok(project);
}
async function checkNodeProject(cwd: string) {
const packageJsonPath = join(cwd, 'package.json');
try {
const rawString = await readFile(packageJsonPath, 'utf-8');
const pkg = JSON.parse(rawString);
// Always prefer start script if it exists
if (pkg.scripts?.start) {
return { type: 'script', script: 'start' } as const;
}
// Try to find the main entrypoint if it exists (if its a TypeScript file, the user has to deal with ensuring their runtime can run it directly)
if (pkg.main) {
try {
await access(resolve(cwd, pkg.main));
return { path: resolve(cwd, pkg.main), type: 'file' } as const;
} catch {
// Ignore errors
}
}
// We have a node project but we don't know what to do with it
return { type: 'unknown-entrypoint' } as const;
} catch {
// Ignore missing package.json and try some common files
}
const filesToCheck = [
join(cwd, 'index.js'),
join(cwd, 'index.mjs'),
join(cwd, 'index.cjs'),
join(cwd, 'main.js'),
join(cwd, 'main.mjs'),
join(cwd, 'main.cjs'),
join(cwd, 'src', 'index.js'),
join(cwd, 'src', 'index.mjs'),
join(cwd, 'src', 'index.cjs'),
join(cwd, 'src', 'main.js'),
join(cwd, 'src', 'main.mjs'),
join(cwd, 'src', 'main.cjs'),
join(cwd, 'dist', 'index.js'),
join(cwd, 'dist', 'index.mjs'),
join(cwd, 'dist', 'index.cjs'),
join(cwd, 'dist', 'main.js'),
join(cwd, 'dist', 'main.mjs'),
join(cwd, 'dist', 'main.cjs'),
];
for (const path of filesToCheck) {
try {
await access(path);
return { path, type: 'file' } as const;
} catch {
// Ignore errors
}
}
return null;
}
async function checkPythonProject(cwd: string) {
const baseName = basename(cwd);
const filesToCheck = [
join(cwd, 'src', '__main__.py'),
join(cwd, '__main__.py'),
join(cwd, baseName, '__main__.py'),
join(cwd, baseName.replaceAll('-', '_').replaceAll(' ', '_'), '__main__.py'),
];
for (const path of filesToCheck) {
try {
await access(path);
// By default in python, we run python3 -m <module>
// For some unholy reason, python does NOT support absolute paths for this -.-
// Effectively, this returns `src` from `/cwd/src/__main__.py`, et al.
return basename(dirname(path));
} catch {
// Ignore errors
}
}
return null;
}
async function checkScrapyProject(cwd: string) {
// TODO: maybe rewrite this to a newer format 🤷
return ScrapyProjectAnalyzer.isApplicable(cwd);
}