@deploid/plugin-doctor 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +57 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +225 -0
- package/dist/index.js.map +1 -0
- package/package.json +14 -0
- package/src/index.ts +365 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
type CheckStatus = 'pass' | 'warn' | 'fail';
|
|
2
|
+
interface CheckResult {
|
|
3
|
+
id: string;
|
|
4
|
+
title: string;
|
|
5
|
+
status: CheckStatus;
|
|
6
|
+
message: string;
|
|
7
|
+
details?: string;
|
|
8
|
+
}
|
|
9
|
+
interface DoctorSummary {
|
|
10
|
+
ok: boolean;
|
|
11
|
+
cwd: string;
|
|
12
|
+
checks: CheckResult[];
|
|
13
|
+
totals: Record<CheckStatus, number>;
|
|
14
|
+
}
|
|
15
|
+
interface DoctorOptions {
|
|
16
|
+
json?: boolean;
|
|
17
|
+
projectOnly?: boolean;
|
|
18
|
+
}
|
|
19
|
+
interface PipelineContext {
|
|
20
|
+
logger: {
|
|
21
|
+
info: (message: string) => void;
|
|
22
|
+
warn: (message: string) => void;
|
|
23
|
+
error: (message: string) => void;
|
|
24
|
+
};
|
|
25
|
+
config: {
|
|
26
|
+
appName: string;
|
|
27
|
+
appId: string;
|
|
28
|
+
web: {
|
|
29
|
+
framework: string;
|
|
30
|
+
buildCommand: string;
|
|
31
|
+
webDir: string;
|
|
32
|
+
};
|
|
33
|
+
android: {
|
|
34
|
+
packaging: string;
|
|
35
|
+
signing?: {
|
|
36
|
+
keystorePath?: string;
|
|
37
|
+
storePasswordEnv?: string;
|
|
38
|
+
keyPasswordEnv?: string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
assets?: {
|
|
42
|
+
source: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
cwd: string;
|
|
46
|
+
doctorOptions?: DoctorOptions;
|
|
47
|
+
}
|
|
48
|
+
declare const plugin: {
|
|
49
|
+
name: string;
|
|
50
|
+
plan: () => string[];
|
|
51
|
+
run: typeof runDoctor;
|
|
52
|
+
};
|
|
53
|
+
declare function runDoctor(ctx: PipelineContext): Promise<void>;
|
|
54
|
+
declare function inspectProject(cwd: string, options?: DoctorOptions): Promise<DoctorSummary>;
|
|
55
|
+
export default plugin;
|
|
56
|
+
export { inspectProject, plugin };
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,UAAU,WAAW;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,aAAa;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;CACrC;AAED,UAAU,aAAa;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,UAAU,eAAe;IACvB,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QAChC,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;QAChC,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;KAClC,CAAC;IACF,MAAM,EAAE;QACN,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,GAAG,EAAE;YAAE,SAAS,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;QACjE,OAAO,EAAE;YACP,SAAS,EAAE,MAAM,CAAC;YAClB,OAAO,CAAC,EAAE;gBACR,YAAY,CAAC,EAAE,MAAM,CAAC;gBACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;gBAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;aACzB,CAAC;SACH,CAAC;QACF,MAAM,CAAC,EAAE;YAAE,MAAM,EAAE,MAAM,CAAA;SAAE,CAAC;KAC7B,CAAC;IACF,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AASD,QAAA,MAAM,MAAM;;;;CAQX,CAAC;AAEF,iBAAe,SAAS,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAY5D;AAED,iBAAe,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAuD1F;AAmOD,eAAe,MAAM,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const CONFIG_CANDIDATES = [
|
|
5
|
+
'deploid.config.ts',
|
|
6
|
+
'deploid.config.js',
|
|
7
|
+
'deploid.config.mjs',
|
|
8
|
+
'deploid.config.cjs'
|
|
9
|
+
];
|
|
10
|
+
const plugin = {
|
|
11
|
+
name: 'doctor',
|
|
12
|
+
plan: () => [
|
|
13
|
+
'Inspect project files and Deploid config',
|
|
14
|
+
'Check required command availability',
|
|
15
|
+
'Report Android setup and release readiness gaps'
|
|
16
|
+
],
|
|
17
|
+
run: runDoctor
|
|
18
|
+
};
|
|
19
|
+
async function runDoctor(ctx) {
|
|
20
|
+
const summary = await inspectProject(ctx.cwd, ctx.doctorOptions);
|
|
21
|
+
if (ctx.doctorOptions?.json) {
|
|
22
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
printSummary(ctx, summary);
|
|
26
|
+
}
|
|
27
|
+
if (!summary.ok) {
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
async function inspectProject(cwd, options) {
|
|
32
|
+
const checks = [];
|
|
33
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
34
|
+
const configPath = findExistingPath(cwd, CONFIG_CANDIDATES);
|
|
35
|
+
const packageJson = readJson(packageJsonPath);
|
|
36
|
+
const config = configPath ? await loadProjectConfig(configPath) : null;
|
|
37
|
+
checks.push(fs.existsSync(packageJsonPath)
|
|
38
|
+
? pass('package-json', 'package.json', 'Found package.json in project root.')
|
|
39
|
+
: fail('package-json', 'package.json', 'package.json is missing from the project root.'));
|
|
40
|
+
checks.push(configPath
|
|
41
|
+
? pass('deploid-config', 'Deploid config', `Found ${path.basename(configPath)}.`)
|
|
42
|
+
: fail('deploid-config', 'Deploid config', 'No Deploid config file was found.'));
|
|
43
|
+
if (config) {
|
|
44
|
+
checks.push(checkWebDir(cwd, config.web?.webDir));
|
|
45
|
+
checks.push(checkAssetsSource(cwd, config.assets?.source));
|
|
46
|
+
checks.push(checkSigning(cwd, config.android?.signing));
|
|
47
|
+
checks.push(checkCapacitorConfig(cwd, config.android?.packaging));
|
|
48
|
+
checks.push(checkAndroidProject(cwd));
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
checks.push(warn('web-output', 'Web output directory', 'Skipped because no Deploid config was loaded.'));
|
|
52
|
+
checks.push(warn('assets-source', 'Asset source', 'Skipped because no Deploid config was loaded.'));
|
|
53
|
+
checks.push(warn('android-signing', 'Android signing', 'Skipped because no Deploid config was loaded.'));
|
|
54
|
+
checks.push(warn('capacitor-config', 'Capacitor config', 'Skipped because no Deploid config was loaded.'));
|
|
55
|
+
checks.push(warn('android-project', 'Android project', 'Skipped because no Deploid config was loaded.'));
|
|
56
|
+
}
|
|
57
|
+
if (!options?.projectOnly) {
|
|
58
|
+
checks.push(await checkCommand('node', ['--version'], 'Node.js', 'Required to run Deploid.'));
|
|
59
|
+
checks.push(await checkCommand('npm', ['--version'], 'npm', 'Used by init, plugin setup, and Capacitor workflows.'));
|
|
60
|
+
checks.push(await checkCommand('npx', ['--version'], 'npx', 'Used to invoke Capacitor CLI commands.'));
|
|
61
|
+
checks.push(await checkCommand('java', ['-version'], 'Java', 'Required for Android builds.'));
|
|
62
|
+
checks.push(await checkCommand('adb', ['version'], 'ADB', 'Required for device listing, deploy, and logs.'));
|
|
63
|
+
checks.push(await checkAndroidSdk());
|
|
64
|
+
checks.push(checkCapacitorDependency(packageJson));
|
|
65
|
+
}
|
|
66
|
+
const totals = {
|
|
67
|
+
pass: checks.filter((check) => check.status === 'pass').length,
|
|
68
|
+
warn: checks.filter((check) => check.status === 'warn').length,
|
|
69
|
+
fail: checks.filter((check) => check.status === 'fail').length
|
|
70
|
+
};
|
|
71
|
+
return {
|
|
72
|
+
ok: totals.fail === 0,
|
|
73
|
+
cwd,
|
|
74
|
+
checks,
|
|
75
|
+
totals
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function printSummary(ctx, summary) {
|
|
79
|
+
ctx.logger.info(`doctor: inspecting ${summary.cwd}`);
|
|
80
|
+
for (const check of summary.checks) {
|
|
81
|
+
const prefix = check.status === 'pass' ? 'PASS' : check.status === 'warn' ? 'WARN' : 'FAIL';
|
|
82
|
+
const line = `${prefix} ${check.title}: ${check.message}`;
|
|
83
|
+
if (check.status === 'pass')
|
|
84
|
+
ctx.logger.info(line);
|
|
85
|
+
if (check.status === 'warn')
|
|
86
|
+
ctx.logger.warn(line);
|
|
87
|
+
if (check.status === 'fail')
|
|
88
|
+
ctx.logger.error(line);
|
|
89
|
+
if (check.details)
|
|
90
|
+
ctx.logger.info(` ${check.details}`);
|
|
91
|
+
}
|
|
92
|
+
ctx.logger.info(`doctor summary: ${summary.totals.pass} passed, ${summary.totals.warn} warnings, ${summary.totals.fail} failures`);
|
|
93
|
+
}
|
|
94
|
+
async function checkCommand(command, args, title, details) {
|
|
95
|
+
const result = spawnSync(command, args, { encoding: 'utf8' });
|
|
96
|
+
if (result.status === 0) {
|
|
97
|
+
const output = `${result.stdout || ''} ${result.stderr || ''}`.trim().split('\n')[0]?.trim();
|
|
98
|
+
return pass(command, title, `${command} is available.`, output || details);
|
|
99
|
+
}
|
|
100
|
+
return fail(command, title, `${command} is not available.`, result.error?.message || result.stderr?.trim() || details);
|
|
101
|
+
}
|
|
102
|
+
async function checkAndroidSdk() {
|
|
103
|
+
const envHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
104
|
+
const sdkPath = envHome || path.join(process.env.HOME || '', 'Android', 'Sdk');
|
|
105
|
+
if (!sdkPath || !fs.existsSync(sdkPath)) {
|
|
106
|
+
return fail('android-sdk', 'Android SDK', 'Android SDK directory was not found.', 'Set ANDROID_HOME or ANDROID_SDK_ROOT, or install the SDK in ~/Android/Sdk.');
|
|
107
|
+
}
|
|
108
|
+
const platformToolsPath = path.join(sdkPath, 'platform-tools');
|
|
109
|
+
if (!fs.existsSync(platformToolsPath)) {
|
|
110
|
+
return warn('android-sdk', 'Android SDK', `SDK found at ${sdkPath}, but platform-tools is missing.`, 'Install Android SDK Platform Tools to enable adb-based workflows.');
|
|
111
|
+
}
|
|
112
|
+
return pass('android-sdk', 'Android SDK', `SDK found at ${sdkPath}.`);
|
|
113
|
+
}
|
|
114
|
+
function checkCapacitorDependency(packageJson) {
|
|
115
|
+
if (!packageJson) {
|
|
116
|
+
return warn('capacitor-dependency', 'Capacitor dependency', 'Skipped because package.json could not be read.');
|
|
117
|
+
}
|
|
118
|
+
const deps = {
|
|
119
|
+
...(asRecord(packageJson.dependencies)),
|
|
120
|
+
...(asRecord(packageJson.devDependencies))
|
|
121
|
+
};
|
|
122
|
+
if (typeof deps['@capacitor/core'] === 'string' || typeof deps['@capacitor/cli'] === 'string') {
|
|
123
|
+
return pass('capacitor-dependency', 'Capacitor dependency', 'Capacitor dependencies are present.');
|
|
124
|
+
}
|
|
125
|
+
return warn('capacitor-dependency', 'Capacitor dependency', 'No Capacitor dependency found in package.json.', 'Run `deploid init` or install @capacitor/core and @capacitor/cli if this project targets Android.');
|
|
126
|
+
}
|
|
127
|
+
function checkWebDir(cwd, webDir) {
|
|
128
|
+
if (!webDir) {
|
|
129
|
+
return warn('web-output', 'Web output directory', 'No `web.webDir` configured.');
|
|
130
|
+
}
|
|
131
|
+
const fullPath = path.join(cwd, webDir);
|
|
132
|
+
if (fs.existsSync(fullPath)) {
|
|
133
|
+
return pass('web-output', 'Web output directory', `Found ${webDir}.`);
|
|
134
|
+
}
|
|
135
|
+
return warn('web-output', 'Web output directory', `${webDir} does not exist yet.`, 'Run your web build before packaging if you expect a ready-to-sync output directory.');
|
|
136
|
+
}
|
|
137
|
+
function checkAssetsSource(cwd, source) {
|
|
138
|
+
if (!source) {
|
|
139
|
+
return warn('assets-source', 'Asset source', 'No `assets.source` configured.');
|
|
140
|
+
}
|
|
141
|
+
const sourcePath = path.join(cwd, source);
|
|
142
|
+
if (fs.existsSync(sourcePath)) {
|
|
143
|
+
return pass('assets-source', 'Asset source', `Found ${source}.`);
|
|
144
|
+
}
|
|
145
|
+
return fail('assets-source', 'Asset source', `${source} does not exist.`, 'Add the source asset or update `assets.source` before running `deploid assets`.');
|
|
146
|
+
}
|
|
147
|
+
function checkSigning(cwd, signing) {
|
|
148
|
+
if (!signing?.keystorePath) {
|
|
149
|
+
return warn('android-signing', 'Android signing', 'No Android signing config found.');
|
|
150
|
+
}
|
|
151
|
+
const keystorePath = path.join(cwd, signing.keystorePath);
|
|
152
|
+
const missingEnvVars = [signing.storePasswordEnv, signing.keyPasswordEnv]
|
|
153
|
+
.filter((name) => Boolean(name))
|
|
154
|
+
.filter((name) => !process.env[name]);
|
|
155
|
+
if (!fs.existsSync(keystorePath)) {
|
|
156
|
+
return fail('android-signing', 'Android signing', `Keystore file is missing: ${signing.keystorePath}.`, 'Create the keystore or fix `android.signing.keystorePath`.');
|
|
157
|
+
}
|
|
158
|
+
if (missingEnvVars.length > 0) {
|
|
159
|
+
return warn('android-signing', 'Android signing', `Keystore found, but env vars are missing: ${missingEnvVars.join(', ')}.`, 'Release builds will fail until those password env vars are exported.');
|
|
160
|
+
}
|
|
161
|
+
return pass('android-signing', 'Android signing', 'Signing keystore and env vars look ready.');
|
|
162
|
+
}
|
|
163
|
+
function checkCapacitorConfig(cwd, packaging) {
|
|
164
|
+
if (packaging !== 'capacitor') {
|
|
165
|
+
return warn('capacitor-config', 'Capacitor config', `Packaging engine is ${packaging || 'unknown'}.`);
|
|
166
|
+
}
|
|
167
|
+
const capacitorConfigPath = path.join(cwd, 'capacitor.config.json');
|
|
168
|
+
if (fs.existsSync(capacitorConfigPath)) {
|
|
169
|
+
return pass('capacitor-config', 'Capacitor config', 'Found capacitor.config.json.');
|
|
170
|
+
}
|
|
171
|
+
return warn('capacitor-config', 'Capacitor config', 'capacitor.config.json is missing.', 'Run `deploid init` or `deploid package` to scaffold Capacitor configuration.');
|
|
172
|
+
}
|
|
173
|
+
function checkAndroidProject(cwd) {
|
|
174
|
+
const androidPath = path.join(cwd, 'android');
|
|
175
|
+
if (fs.existsSync(androidPath)) {
|
|
176
|
+
return pass('android-project', 'Android project', 'Found android/ project.');
|
|
177
|
+
}
|
|
178
|
+
return warn('android-project', 'Android project', 'android/ project has not been generated yet.', 'Run `deploid package` before building or deploying Android artifacts.');
|
|
179
|
+
}
|
|
180
|
+
function pass(id, title, message, details) {
|
|
181
|
+
return { id, title, status: 'pass', message, details };
|
|
182
|
+
}
|
|
183
|
+
function warn(id, title, message, details) {
|
|
184
|
+
return { id, title, status: 'warn', message, details };
|
|
185
|
+
}
|
|
186
|
+
function fail(id, title, message, details) {
|
|
187
|
+
return { id, title, status: 'fail', message, details };
|
|
188
|
+
}
|
|
189
|
+
function findExistingPath(cwd, candidates) {
|
|
190
|
+
for (const candidate of candidates) {
|
|
191
|
+
const fullPath = path.join(cwd, candidate);
|
|
192
|
+
if (fs.existsSync(fullPath))
|
|
193
|
+
return fullPath;
|
|
194
|
+
}
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
async function loadProjectConfig(configPath) {
|
|
198
|
+
try {
|
|
199
|
+
const mod = await import(pathToFileUrl(configPath).href);
|
|
200
|
+
return (mod.default || mod);
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
function pathToFileUrl(filePath) {
|
|
207
|
+
const resolved = path.resolve(filePath);
|
|
208
|
+
const url = new URL('file://');
|
|
209
|
+
url.pathname = resolved;
|
|
210
|
+
return url;
|
|
211
|
+
}
|
|
212
|
+
function readJson(filePath) {
|
|
213
|
+
try {
|
|
214
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function asRecord(value) {
|
|
221
|
+
return typeof value === 'object' && value !== null ? value : {};
|
|
222
|
+
}
|
|
223
|
+
export default plugin;
|
|
224
|
+
export { inspectProject, plugin };
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
package/src/index.ts
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
type CheckStatus = 'pass' | 'warn' | 'fail';
|
|
6
|
+
|
|
7
|
+
interface CheckResult {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
status: CheckStatus;
|
|
11
|
+
message: string;
|
|
12
|
+
details?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface DoctorSummary {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
cwd: string;
|
|
18
|
+
checks: CheckResult[];
|
|
19
|
+
totals: Record<CheckStatus, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface DoctorOptions {
|
|
23
|
+
json?: boolean;
|
|
24
|
+
projectOnly?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface PipelineContext {
|
|
28
|
+
logger: {
|
|
29
|
+
info: (message: string) => void;
|
|
30
|
+
warn: (message: string) => void;
|
|
31
|
+
error: (message: string) => void;
|
|
32
|
+
};
|
|
33
|
+
config: {
|
|
34
|
+
appName: string;
|
|
35
|
+
appId: string;
|
|
36
|
+
web: { framework: string; buildCommand: string; webDir: string };
|
|
37
|
+
android: {
|
|
38
|
+
packaging: string;
|
|
39
|
+
signing?: {
|
|
40
|
+
keystorePath?: string;
|
|
41
|
+
storePasswordEnv?: string;
|
|
42
|
+
keyPasswordEnv?: string;
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
assets?: { source: string };
|
|
46
|
+
};
|
|
47
|
+
cwd: string;
|
|
48
|
+
doctorOptions?: DoctorOptions;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const CONFIG_CANDIDATES = [
|
|
52
|
+
'deploid.config.ts',
|
|
53
|
+
'deploid.config.js',
|
|
54
|
+
'deploid.config.mjs',
|
|
55
|
+
'deploid.config.cjs'
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const plugin = {
|
|
59
|
+
name: 'doctor',
|
|
60
|
+
plan: () => [
|
|
61
|
+
'Inspect project files and Deploid config',
|
|
62
|
+
'Check required command availability',
|
|
63
|
+
'Report Android setup and release readiness gaps'
|
|
64
|
+
],
|
|
65
|
+
run: runDoctor
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
async function runDoctor(ctx: PipelineContext): Promise<void> {
|
|
69
|
+
const summary = await inspectProject(ctx.cwd, ctx.doctorOptions);
|
|
70
|
+
|
|
71
|
+
if (ctx.doctorOptions?.json) {
|
|
72
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
73
|
+
} else {
|
|
74
|
+
printSummary(ctx, summary);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!summary.ok) {
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function inspectProject(cwd: string, options?: DoctorOptions): Promise<DoctorSummary> {
|
|
83
|
+
const checks: CheckResult[] = [];
|
|
84
|
+
const packageJsonPath = path.join(cwd, 'package.json');
|
|
85
|
+
const configPath = findExistingPath(cwd, CONFIG_CANDIDATES);
|
|
86
|
+
const packageJson = readJson<Record<string, unknown>>(packageJsonPath);
|
|
87
|
+
const config = configPath ? await loadProjectConfig(configPath) : null;
|
|
88
|
+
|
|
89
|
+
checks.push(
|
|
90
|
+
fs.existsSync(packageJsonPath)
|
|
91
|
+
? pass('package-json', 'package.json', 'Found package.json in project root.')
|
|
92
|
+
: fail('package-json', 'package.json', 'package.json is missing from the project root.')
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
checks.push(
|
|
96
|
+
configPath
|
|
97
|
+
? pass('deploid-config', 'Deploid config', `Found ${path.basename(configPath)}.`)
|
|
98
|
+
: fail('deploid-config', 'Deploid config', 'No Deploid config file was found.')
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (config) {
|
|
102
|
+
checks.push(checkWebDir(cwd, config.web?.webDir));
|
|
103
|
+
checks.push(checkAssetsSource(cwd, config.assets?.source));
|
|
104
|
+
checks.push(checkSigning(cwd, config.android?.signing));
|
|
105
|
+
checks.push(checkCapacitorConfig(cwd, config.android?.packaging));
|
|
106
|
+
checks.push(checkAndroidProject(cwd));
|
|
107
|
+
} else {
|
|
108
|
+
checks.push(warn('web-output', 'Web output directory', 'Skipped because no Deploid config was loaded.'));
|
|
109
|
+
checks.push(warn('assets-source', 'Asset source', 'Skipped because no Deploid config was loaded.'));
|
|
110
|
+
checks.push(warn('android-signing', 'Android signing', 'Skipped because no Deploid config was loaded.'));
|
|
111
|
+
checks.push(warn('capacitor-config', 'Capacitor config', 'Skipped because no Deploid config was loaded.'));
|
|
112
|
+
checks.push(warn('android-project', 'Android project', 'Skipped because no Deploid config was loaded.'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!options?.projectOnly) {
|
|
116
|
+
checks.push(await checkCommand('node', ['--version'], 'Node.js', 'Required to run Deploid.'));
|
|
117
|
+
checks.push(await checkCommand('npm', ['--version'], 'npm', 'Used by init, plugin setup, and Capacitor workflows.'));
|
|
118
|
+
checks.push(await checkCommand('npx', ['--version'], 'npx', 'Used to invoke Capacitor CLI commands.'));
|
|
119
|
+
checks.push(await checkCommand('java', ['-version'], 'Java', 'Required for Android builds.'));
|
|
120
|
+
checks.push(await checkCommand('adb', ['version'], 'ADB', 'Required for device listing, deploy, and logs.'));
|
|
121
|
+
checks.push(await checkAndroidSdk());
|
|
122
|
+
checks.push(checkCapacitorDependency(packageJson));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const totals = {
|
|
126
|
+
pass: checks.filter((check) => check.status === 'pass').length,
|
|
127
|
+
warn: checks.filter((check) => check.status === 'warn').length,
|
|
128
|
+
fail: checks.filter((check) => check.status === 'fail').length
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
ok: totals.fail === 0,
|
|
133
|
+
cwd,
|
|
134
|
+
checks,
|
|
135
|
+
totals
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function printSummary(ctx: PipelineContext, summary: DoctorSummary): void {
|
|
140
|
+
ctx.logger.info(`doctor: inspecting ${summary.cwd}`);
|
|
141
|
+
|
|
142
|
+
for (const check of summary.checks) {
|
|
143
|
+
const prefix = check.status === 'pass' ? 'PASS' : check.status === 'warn' ? 'WARN' : 'FAIL';
|
|
144
|
+
const line = `${prefix} ${check.title}: ${check.message}`;
|
|
145
|
+
if (check.status === 'pass') ctx.logger.info(line);
|
|
146
|
+
if (check.status === 'warn') ctx.logger.warn(line);
|
|
147
|
+
if (check.status === 'fail') ctx.logger.error(line);
|
|
148
|
+
if (check.details) ctx.logger.info(` ${check.details}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ctx.logger.info(
|
|
152
|
+
`doctor summary: ${summary.totals.pass} passed, ${summary.totals.warn} warnings, ${summary.totals.fail} failures`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async function checkCommand(command: string, args: string[], title: string, details: string): Promise<CheckResult> {
|
|
157
|
+
const result = spawnSync(command, args, { encoding: 'utf8' });
|
|
158
|
+
if (result.status === 0) {
|
|
159
|
+
const output = `${result.stdout || ''} ${result.stderr || ''}`.trim().split('\n')[0]?.trim();
|
|
160
|
+
return pass(command, title, `${command} is available.`, output || details);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return fail(command, title, `${command} is not available.`, result.error?.message || result.stderr?.trim() || details);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function checkAndroidSdk(): Promise<CheckResult> {
|
|
167
|
+
const envHome = process.env.ANDROID_HOME || process.env.ANDROID_SDK_ROOT;
|
|
168
|
+
const sdkPath = envHome || path.join(process.env.HOME || '', 'Android', 'Sdk');
|
|
169
|
+
|
|
170
|
+
if (!sdkPath || !fs.existsSync(sdkPath)) {
|
|
171
|
+
return fail(
|
|
172
|
+
'android-sdk',
|
|
173
|
+
'Android SDK',
|
|
174
|
+
'Android SDK directory was not found.',
|
|
175
|
+
'Set ANDROID_HOME or ANDROID_SDK_ROOT, or install the SDK in ~/Android/Sdk.'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const platformToolsPath = path.join(sdkPath, 'platform-tools');
|
|
180
|
+
if (!fs.existsSync(platformToolsPath)) {
|
|
181
|
+
return warn(
|
|
182
|
+
'android-sdk',
|
|
183
|
+
'Android SDK',
|
|
184
|
+
`SDK found at ${sdkPath}, but platform-tools is missing.`,
|
|
185
|
+
'Install Android SDK Platform Tools to enable adb-based workflows.'
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return pass('android-sdk', 'Android SDK', `SDK found at ${sdkPath}.`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function checkCapacitorDependency(packageJson: Record<string, unknown> | null): CheckResult {
|
|
193
|
+
if (!packageJson) {
|
|
194
|
+
return warn('capacitor-dependency', 'Capacitor dependency', 'Skipped because package.json could not be read.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const deps = {
|
|
198
|
+
...(asRecord(packageJson.dependencies)),
|
|
199
|
+
...(asRecord(packageJson.devDependencies))
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (typeof deps['@capacitor/core'] === 'string' || typeof deps['@capacitor/cli'] === 'string') {
|
|
203
|
+
return pass('capacitor-dependency', 'Capacitor dependency', 'Capacitor dependencies are present.');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return warn(
|
|
207
|
+
'capacitor-dependency',
|
|
208
|
+
'Capacitor dependency',
|
|
209
|
+
'No Capacitor dependency found in package.json.',
|
|
210
|
+
'Run `deploid init` or install @capacitor/core and @capacitor/cli if this project targets Android.'
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function checkWebDir(cwd: string, webDir: string | undefined): CheckResult {
|
|
215
|
+
if (!webDir) {
|
|
216
|
+
return warn('web-output', 'Web output directory', 'No `web.webDir` configured.');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const fullPath = path.join(cwd, webDir);
|
|
220
|
+
if (fs.existsSync(fullPath)) {
|
|
221
|
+
return pass('web-output', 'Web output directory', `Found ${webDir}.`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return warn(
|
|
225
|
+
'web-output',
|
|
226
|
+
'Web output directory',
|
|
227
|
+
`${webDir} does not exist yet.`,
|
|
228
|
+
'Run your web build before packaging if you expect a ready-to-sync output directory.'
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function checkAssetsSource(cwd: string, source: string | undefined): CheckResult {
|
|
233
|
+
if (!source) {
|
|
234
|
+
return warn('assets-source', 'Asset source', 'No `assets.source` configured.');
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const sourcePath = path.join(cwd, source);
|
|
238
|
+
if (fs.existsSync(sourcePath)) {
|
|
239
|
+
return pass('assets-source', 'Asset source', `Found ${source}.`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return fail(
|
|
243
|
+
'assets-source',
|
|
244
|
+
'Asset source',
|
|
245
|
+
`${source} does not exist.`,
|
|
246
|
+
'Add the source asset or update `assets.source` before running `deploid assets`.'
|
|
247
|
+
);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function checkSigning(
|
|
251
|
+
cwd: string,
|
|
252
|
+
signing: { keystorePath?: string; storePasswordEnv?: string; keyPasswordEnv?: string } | undefined
|
|
253
|
+
): CheckResult {
|
|
254
|
+
if (!signing?.keystorePath) {
|
|
255
|
+
return warn('android-signing', 'Android signing', 'No Android signing config found.');
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const keystorePath = path.join(cwd, signing.keystorePath);
|
|
259
|
+
const missingEnvVars = [signing.storePasswordEnv, signing.keyPasswordEnv]
|
|
260
|
+
.filter((name): name is string => Boolean(name))
|
|
261
|
+
.filter((name) => !process.env[name]);
|
|
262
|
+
|
|
263
|
+
if (!fs.existsSync(keystorePath)) {
|
|
264
|
+
return fail(
|
|
265
|
+
'android-signing',
|
|
266
|
+
'Android signing',
|
|
267
|
+
`Keystore file is missing: ${signing.keystorePath}.`,
|
|
268
|
+
'Create the keystore or fix `android.signing.keystorePath`.'
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (missingEnvVars.length > 0) {
|
|
273
|
+
return warn(
|
|
274
|
+
'android-signing',
|
|
275
|
+
'Android signing',
|
|
276
|
+
`Keystore found, but env vars are missing: ${missingEnvVars.join(', ')}.`,
|
|
277
|
+
'Release builds will fail until those password env vars are exported.'
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return pass('android-signing', 'Android signing', 'Signing keystore and env vars look ready.');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function checkCapacitorConfig(cwd: string, packaging: string | undefined): CheckResult {
|
|
285
|
+
if (packaging !== 'capacitor') {
|
|
286
|
+
return warn('capacitor-config', 'Capacitor config', `Packaging engine is ${packaging || 'unknown'}.`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const capacitorConfigPath = path.join(cwd, 'capacitor.config.json');
|
|
290
|
+
if (fs.existsSync(capacitorConfigPath)) {
|
|
291
|
+
return pass('capacitor-config', 'Capacitor config', 'Found capacitor.config.json.');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return warn(
|
|
295
|
+
'capacitor-config',
|
|
296
|
+
'Capacitor config',
|
|
297
|
+
'capacitor.config.json is missing.',
|
|
298
|
+
'Run `deploid init` or `deploid package` to scaffold Capacitor configuration.'
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function checkAndroidProject(cwd: string): CheckResult {
|
|
303
|
+
const androidPath = path.join(cwd, 'android');
|
|
304
|
+
if (fs.existsSync(androidPath)) {
|
|
305
|
+
return pass('android-project', 'Android project', 'Found android/ project.');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return warn(
|
|
309
|
+
'android-project',
|
|
310
|
+
'Android project',
|
|
311
|
+
'android/ project has not been generated yet.',
|
|
312
|
+
'Run `deploid package` before building or deploying Android artifacts.'
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function pass(id: string, title: string, message: string, details?: string): CheckResult {
|
|
317
|
+
return { id, title, status: 'pass', message, details };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
function warn(id: string, title: string, message: string, details?: string): CheckResult {
|
|
321
|
+
return { id, title, status: 'warn', message, details };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function fail(id: string, title: string, message: string, details?: string): CheckResult {
|
|
325
|
+
return { id, title, status: 'fail', message, details };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function findExistingPath(cwd: string, candidates: string[]): string | null {
|
|
329
|
+
for (const candidate of candidates) {
|
|
330
|
+
const fullPath = path.join(cwd, candidate);
|
|
331
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function loadProjectConfig(configPath: string): Promise<Record<string, any> | null> {
|
|
337
|
+
try {
|
|
338
|
+
const mod = await import(pathToFileUrl(configPath).href);
|
|
339
|
+
return (mod.default || mod) as Record<string, any>;
|
|
340
|
+
} catch {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function pathToFileUrl(filePath: string): URL {
|
|
346
|
+
const resolved = path.resolve(filePath);
|
|
347
|
+
const url = new URL('file://');
|
|
348
|
+
url.pathname = resolved;
|
|
349
|
+
return url;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
function readJson<T>(filePath: string): T | null {
|
|
353
|
+
try {
|
|
354
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8')) as T;
|
|
355
|
+
} catch {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
361
|
+
return typeof value === 'object' && value !== null ? (value as Record<string, unknown>) : {};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export default plugin;
|
|
365
|
+
export { inspectProject, plugin };
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts"],"version":"5.9.3"}
|