@capuchoo/cli 0.2.1 → 0.4.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/README.md +233 -26
- package/dist/base-command.d.ts +1 -1
- package/dist/base-command.js +1 -1
- package/dist/cli/prompts.d.ts +43 -0
- package/dist/cli/prompts.d.ts.map +1 -0
- package/dist/cli/prompts.js +137 -0
- package/dist/cli/prompts.js.map +1 -0
- package/dist/commands/app/delete.d.ts +20 -0
- package/dist/commands/app/delete.d.ts.map +1 -0
- package/dist/commands/app/delete.js +71 -0
- package/dist/commands/app/delete.js.map +1 -0
- package/dist/commands/app/list.d.ts +9 -0
- package/dist/commands/app/list.d.ts.map +1 -0
- package/dist/commands/app/list.js +40 -0
- package/dist/commands/app/list.js.map +1 -0
- package/dist/commands/auth/login.d.ts.map +1 -1
- package/dist/commands/auth/login.js +10 -13
- package/dist/commands/auth/login.js.map +1 -1
- package/dist/commands/auth/logout.js +2 -2
- package/dist/commands/auth/logout.js.map +1 -1
- package/dist/commands/auth/whoami.d.ts.map +1 -1
- package/dist/commands/auth/whoami.js +14 -2
- package/dist/commands/auth/whoami.js.map +1 -1
- package/dist/commands/channel/create.d.ts +25 -0
- package/dist/commands/channel/create.d.ts.map +1 -0
- package/dist/commands/channel/create.js +96 -0
- package/dist/commands/channel/create.js.map +1 -0
- package/dist/commands/channel/delete.d.ts +19 -0
- package/dist/commands/channel/delete.d.ts.map +1 -0
- package/dist/commands/channel/delete.js +66 -0
- package/dist/commands/channel/delete.js.map +1 -0
- package/dist/commands/channel/list.js +1 -1
- package/dist/commands/channel/list.js.map +1 -1
- package/dist/commands/doctor.d.ts +28 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +278 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +151 -58
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/org/create.d.ts +19 -0
- package/dist/commands/org/create.d.ts.map +1 -0
- package/dist/commands/org/create.js +45 -0
- package/dist/commands/org/create.js.map +1 -0
- package/dist/commands/org/list.d.ts +9 -0
- package/dist/commands/org/list.d.ts.map +1 -0
- package/dist/commands/org/list.js +41 -0
- package/dist/commands/org/list.js.map +1 -0
- package/dist/commands/setup.d.ts +16 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +168 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/deploy/execute.d.ts.map +1 -1
- package/dist/deploy/execute.js +43 -30
- package/dist/deploy/execute.js.map +1 -1
- package/dist/pipeline/build.js +1 -1
- package/dist/pipeline/build.js.map +1 -1
- package/dist/pipeline/capacitor-support.d.ts +53 -0
- package/dist/pipeline/capacitor-support.d.ts.map +1 -0
- package/dist/pipeline/capacitor-support.js +95 -0
- package/dist/pipeline/capacitor-support.js.map +1 -0
- package/dist/pipeline/deploy.js +1 -1
- package/dist/pipeline/deploy.js.map +1 -1
- package/dist/pipeline/flavour.js +1 -1
- package/dist/pipeline/flavour.js.map +1 -1
- package/dist/services/cloud.d.ts +22 -2
- package/dist/services/cloud.d.ts.map +1 -1
- package/dist/services/cloud.js +26 -3
- package/dist/services/cloud.js.map +1 -1
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.js +3 -3
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/http.d.ts +2 -0
- package/dist/utils/http.d.ts.map +1 -1
- package/dist/utils/http.js +4 -0
- package/dist/utils/http.js.map +1 -1
- package/oclif.manifest.json +352 -5
- package/package.json +13 -6
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { canPublishTo, hasEnvironmentMismatch, normaliseProjectConfig, suggestEnvironment, } from "@capuchoo/core";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { BaseCommand } from "../base-command.js";
|
|
6
|
+
import { CloudClient } from "../services/cloud.js";
|
|
7
|
+
import { readProjectConfig, resolveCredentials } from "../utils/config.js";
|
|
8
|
+
/**
|
|
9
|
+
* Explains why a deploy will not work, before you run one.
|
|
10
|
+
*
|
|
11
|
+
* Every check here corresponds to something that has silently failed in this
|
|
12
|
+
* project: an endpoint pointing at a retired backend, a channel whose
|
|
13
|
+
* environment disagreed with its name, a channel serving nothing because the
|
|
14
|
+
* upload never moved its pointer, an app whose flavour env file did not exist.
|
|
15
|
+
* Each finding names the fix, because a diagnosis you cannot act on is just bad
|
|
16
|
+
* news.
|
|
17
|
+
*/
|
|
18
|
+
export default class Doctor extends BaseCommand {
|
|
19
|
+
static description = "Check that this app, its credentials and its channels are usable";
|
|
20
|
+
static examples = ["<%= config.bin %> doctor"];
|
|
21
|
+
async run() {
|
|
22
|
+
const appDir = process.cwd();
|
|
23
|
+
const findings = [];
|
|
24
|
+
// --- credentials ---------------------------------------------------------
|
|
25
|
+
const credentials = resolveCredentials();
|
|
26
|
+
if (!credentials) {
|
|
27
|
+
this.report([{ level: "fail", what: "Not signed in", fix: "capuchoo auth login" }]);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
findings.push({ level: "ok", what: "Endpoint", detail: credentials.endpoint });
|
|
31
|
+
const cloud = new CloudClient(credentials.endpoint, credentials.apiKey);
|
|
32
|
+
const profile = await cloud.whoami().catch(() => null);
|
|
33
|
+
if (!profile) {
|
|
34
|
+
findings.push({
|
|
35
|
+
level: "fail",
|
|
36
|
+
what: "Credentials rejected",
|
|
37
|
+
detail: `${credentials.endpoint} did not accept the stored API key`,
|
|
38
|
+
// The endpoint is the likelier culprit: a retired backend answers
|
|
39
|
+
// /health but validates nothing.
|
|
40
|
+
fix: `capuchoo config set endpoint <url> (or capuchoo auth login)`,
|
|
41
|
+
});
|
|
42
|
+
this.report(findings);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
findings.push({ level: "ok", what: "Signed in", detail: profile.user.email });
|
|
46
|
+
// --- the link between this directory and a cloud app ---------------------
|
|
47
|
+
// Normalised, not raw: a v1 project.json carries only the cloud identifiers
|
|
48
|
+
// and every flavour is defaulted at runtime. Reading the raw fields reported
|
|
49
|
+
// "no flavour configured" for an app that deploys fine.
|
|
50
|
+
const raw = readProjectConfig(appDir);
|
|
51
|
+
const project = raw ? normaliseProjectConfig(raw) : null;
|
|
52
|
+
if (!project) {
|
|
53
|
+
findings.push({
|
|
54
|
+
level: "fail",
|
|
55
|
+
what: "This directory is not linked to an app",
|
|
56
|
+
fix: "capuchoo init",
|
|
57
|
+
});
|
|
58
|
+
this.report(findings);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
findings.push({
|
|
62
|
+
level: "ok",
|
|
63
|
+
what: "Linked",
|
|
64
|
+
detail: `${project.appName} (${project.appId})`,
|
|
65
|
+
});
|
|
66
|
+
// An app-scoped key lists every app the account owns and is refused only by
|
|
67
|
+
// the endpoints that publish - so without this check the first sign of a
|
|
68
|
+
// mismatch is a 403 at step 7 of 7, after a full build and zip.
|
|
69
|
+
if (!canPublishTo(profile, project.cloudAppId)) {
|
|
70
|
+
const scoped = profile.apps.find((app) => app.id === profile.credential?.app_id);
|
|
71
|
+
findings.push({
|
|
72
|
+
level: "fail",
|
|
73
|
+
what: "This API key cannot publish to this app",
|
|
74
|
+
detail: scoped
|
|
75
|
+
? `The key is restricted to ${scoped.name} (${scoped.app_id})`
|
|
76
|
+
: `The key is restricted to app ${profile.credential?.app_id}`,
|
|
77
|
+
fix: "capuchoo auth login (with a key for this app, or an unscoped one)",
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// --- channels ------------------------------------------------------------
|
|
81
|
+
const channels = await cloud.channels(project.cloudAppId).catch(() => null);
|
|
82
|
+
if (!channels) {
|
|
83
|
+
findings.push({
|
|
84
|
+
level: "fail",
|
|
85
|
+
what: "The linked cloud app could not be read",
|
|
86
|
+
detail: `cloudAppId ${project.cloudAppId} - deleted, or belongs to another account`,
|
|
87
|
+
fix: "capuchoo init --force",
|
|
88
|
+
});
|
|
89
|
+
this.report(findings);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (channels.length === 0) {
|
|
93
|
+
findings.push({
|
|
94
|
+
level: "fail",
|
|
95
|
+
what: "No channels",
|
|
96
|
+
detail: "Nothing can be deployed until a channel exists",
|
|
97
|
+
fix: "capuchoo channel create staging",
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
for (const channel of channels) {
|
|
101
|
+
if (!channel.environment) {
|
|
102
|
+
findings.push({
|
|
103
|
+
level: "fail",
|
|
104
|
+
what: `Channel "${channel.name}" has no environment`,
|
|
105
|
+
detail: "The environment is what tells the CLI which flavour to build",
|
|
106
|
+
fix: "Recreate it with capuchoo channel create, or set the environment " +
|
|
107
|
+
`in the dashboard (suggested: ${suggestEnvironment(channel.name) ?? "prod"})`,
|
|
108
|
+
});
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
if (hasEnvironmentMismatch(channel.name, channel.environment)) {
|
|
112
|
+
findings.push({
|
|
113
|
+
level: "warn",
|
|
114
|
+
what: `Channel "${channel.name}" is on the ${channel.environment} environment`,
|
|
115
|
+
detail: `Devices on it receive ${channel.environment} bundles, built from .env.${channel.environment}`,
|
|
116
|
+
fix: `Set it to ${suggestEnvironment(channel.name)} unless that is deliberate`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// The pointer that decides what is served. An upload that does not move
|
|
120
|
+
// it leaves the channel answering "no update" forever.
|
|
121
|
+
if (!channel.current_version_id) {
|
|
122
|
+
findings.push({
|
|
123
|
+
level: "warn",
|
|
124
|
+
what: `Channel "${channel.name}" is serving no bundle`,
|
|
125
|
+
detail: "No OTA version is active on it yet",
|
|
126
|
+
fix: `capuchoo deploy ota --channel ${channel.name}`,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// --- flavours ------------------------------------------------------------
|
|
131
|
+
const environments = new Set(channels.map((channel) => channel.environment).filter(Boolean));
|
|
132
|
+
for (const environment of environments) {
|
|
133
|
+
const flavour = project.flavours[environment];
|
|
134
|
+
const envFile = path.join(appDir, flavour.envFile);
|
|
135
|
+
if (!fs.existsSync(envFile)) {
|
|
136
|
+
findings.push({
|
|
137
|
+
level: "fail",
|
|
138
|
+
what: `Missing ${flavour.envFile}`,
|
|
139
|
+
detail: `A ${environment} deploy reads it for the app id, version and update URL`,
|
|
140
|
+
fix: `Create ${flavour.envFile}`,
|
|
141
|
+
});
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
const contents = fs.readFileSync(envFile, "utf8");
|
|
145
|
+
const updateUrl = /^VITE_UPDATE_API_URL=(.*)$/m.exec(contents)?.[1]?.trim();
|
|
146
|
+
if (!updateUrl) {
|
|
147
|
+
findings.push({
|
|
148
|
+
level: "fail",
|
|
149
|
+
what: `${flavour.envFile} has no VITE_UPDATE_API_URL`,
|
|
150
|
+
detail: "The app would ship with updates silently disabled",
|
|
151
|
+
fix: `Add VITE_UPDATE_API_URL=${credentials.endpoint}`,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
else if (updateUrl.replace(/\/+$/, "") !== credentials.endpoint.replace(/\/+$/, "")) {
|
|
155
|
+
// Not necessarily wrong - a custom domain in front of the same service
|
|
156
|
+
// is good practice - but it is the mistake that cost an afternoon.
|
|
157
|
+
findings.push({
|
|
158
|
+
level: "warn",
|
|
159
|
+
what: `${flavour.envFile} points at a different host`,
|
|
160
|
+
detail: `app: ${updateUrl} cli: ${credentials.endpoint}`,
|
|
161
|
+
fix: "Confirm both reach the same backend",
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
findings.push({ level: "ok", what: `Flavour ${environment}`, detail: flavour.envFile });
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// --- the app's own wiring ------------------------------------------------
|
|
169
|
+
const webDir = path.join(appDir, project.webDir ?? "dist");
|
|
170
|
+
if (!fs.existsSync(webDir)) {
|
|
171
|
+
findings.push({
|
|
172
|
+
level: "warn",
|
|
173
|
+
what: `webDir "${project.webDir}" does not exist yet`,
|
|
174
|
+
detail: "Fine before the first build; a deploy builds it",
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
findings.push(...this.checkRuntimeWiring(appDir));
|
|
178
|
+
this.report(findings);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Looks for the two calls that decide whether updates work at all.
|
|
182
|
+
*
|
|
183
|
+
* Textual, not semantic: this is a hint, not a compiler. Worth doing anyway -
|
|
184
|
+
* a missing `notifyAppReady()` rolls back working bundles ten seconds after
|
|
185
|
+
* they install, and that is invisible until it happens on a device.
|
|
186
|
+
*/
|
|
187
|
+
checkRuntimeWiring(appDir) {
|
|
188
|
+
const findings = [];
|
|
189
|
+
const pkgPath = path.join(appDir, "package.json");
|
|
190
|
+
if (!fs.existsSync(pkgPath))
|
|
191
|
+
return findings;
|
|
192
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
193
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
194
|
+
if (!deps["@capuchoo/updater"]) {
|
|
195
|
+
findings.push({
|
|
196
|
+
level: "warn",
|
|
197
|
+
what: "@capuchoo/updater is not a dependency",
|
|
198
|
+
detail: "Deploys will work; the app will not check for what they publish",
|
|
199
|
+
fix: "pnpm add @capuchoo/updater",
|
|
200
|
+
});
|
|
201
|
+
return findings;
|
|
202
|
+
}
|
|
203
|
+
const sources = this.sourceFiles(path.join(appDir, "src"));
|
|
204
|
+
const joined = sources.map((file) => fs.readFileSync(file, "utf8")).join("\n");
|
|
205
|
+
if (!joined.includes("notifyAppReady")) {
|
|
206
|
+
findings.push({
|
|
207
|
+
level: "fail",
|
|
208
|
+
what: "notifyAppReady() is never called",
|
|
209
|
+
detail: "The plugin rolls back to the previous bundle when it does not hear this " +
|
|
210
|
+
"within its timeout - so every update would install and then revert",
|
|
211
|
+
fix: 'Call it early in main.ts: import { notifyAppReady } from "@capuchoo/updater"',
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
else {
|
|
215
|
+
findings.push({ level: "ok", what: "notifyAppReady() present" });
|
|
216
|
+
}
|
|
217
|
+
const capacitorConfig = ["capacitor.config.ts", "capacitor.config.js"]
|
|
218
|
+
.map((name) => path.join(appDir, name))
|
|
219
|
+
.find((file) => fs.existsSync(file));
|
|
220
|
+
if (capacitorConfig) {
|
|
221
|
+
const config = fs.readFileSync(capacitorConfig, "utf8");
|
|
222
|
+
if (!config.includes("capuchooUpdaterConfig") && !config.includes("capuchooUpdaterConfig")) {
|
|
223
|
+
findings.push({
|
|
224
|
+
level: "warn",
|
|
225
|
+
what: `${path.basename(capacitorConfig)} does not use capuchooUpdaterConfig()`,
|
|
226
|
+
detail: "Hand-written plugin config has shipped an empty updateUrl before, which " +
|
|
227
|
+
"disables updates without failing",
|
|
228
|
+
fix: "plugins.CapacitorUpdater = capuchooUpdaterConfig({ apiUrl, channel })",
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
findings.push({ level: "ok", what: "Capacitor plugin config" });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return findings;
|
|
236
|
+
}
|
|
237
|
+
/** Shallow-ish walk: enough to find an import, cheap enough to run always. */
|
|
238
|
+
sourceFiles(dir, depth = 0) {
|
|
239
|
+
if (depth > 3 || !fs.existsSync(dir))
|
|
240
|
+
return [];
|
|
241
|
+
const found = [];
|
|
242
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
243
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
244
|
+
continue;
|
|
245
|
+
const full = path.join(dir, entry.name);
|
|
246
|
+
if (entry.isDirectory())
|
|
247
|
+
found.push(...this.sourceFiles(full, depth + 1));
|
|
248
|
+
else if (/\.(ts|tsx|js|vue)$/.test(entry.name))
|
|
249
|
+
found.push(full);
|
|
250
|
+
}
|
|
251
|
+
return found;
|
|
252
|
+
}
|
|
253
|
+
report(findings) {
|
|
254
|
+
const mark = { ok: chalk.green("✓"), warn: chalk.yellow("!"), fail: chalk.red("✗") };
|
|
255
|
+
this.log("");
|
|
256
|
+
for (const finding of findings) {
|
|
257
|
+
this.log(` ${mark[finding.level]} ${finding.what}`);
|
|
258
|
+
if (finding.detail)
|
|
259
|
+
this.log(chalk.dim(` ${finding.detail}`));
|
|
260
|
+
if (finding.fix)
|
|
261
|
+
this.log(chalk.cyan(` → ${finding.fix}`));
|
|
262
|
+
}
|
|
263
|
+
const failed = findings.filter((f) => f.level === "fail").length;
|
|
264
|
+
const warned = findings.filter((f) => f.level === "warn").length;
|
|
265
|
+
this.log("");
|
|
266
|
+
if (failed === 0 && warned === 0) {
|
|
267
|
+
this.log(chalk.green(" Ready to deploy."));
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
this.log(` ${failed} blocking, ${warned} worth a look. ` +
|
|
271
|
+
chalk.dim("Fix the blocking ones before deploying."));
|
|
272
|
+
}
|
|
273
|
+
this.log("");
|
|
274
|
+
if (failed > 0)
|
|
275
|
+
process.exitCode = 1;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -4,9 +4,20 @@ export default class Init extends BaseCommand {
|
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
link: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
create: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
"app-id": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
org: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
channel: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
12
|
force: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
13
|
};
|
|
9
14
|
run(): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates the first channel, because a linked app without one cannot be
|
|
17
|
+
* deployed to. The environment is chosen explicitly: a channel's name never
|
|
18
|
+
* decides which flavour it serves.
|
|
19
|
+
*/
|
|
20
|
+
private offerFirstChannel;
|
|
10
21
|
/** Detects which flavours the project actually has files for. */
|
|
11
22
|
private detectFlavours;
|
|
12
23
|
/** Reads `webDir` out of capacitor.config.* so the CLI zips the right folder. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AASjD,MAAM,CAAC,OAAO,OAAO,IAAK,SAAQ,WAAW;IAC3C,OAAgB,WAAW,SACgD;IAE3E,OAAgB,QAAQ,WAKtB;IAEF,OAAgB,KAAK;;;;;;;;MAqCnB;IAEI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;IA8J1B;;;;OAIG;YACW,iBAAiB;IAmE/B,iEAAiE;IACjE,OAAO,CAAC,cAAc;IAatB,iFAAiF;IACjF,OAAO,CAAC,YAAY;YAYN,YAAY;YA2BZ,SAAS;CAoFxB"}
|
package/dist/commands/init.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { confirm,
|
|
2
|
-
import { ENVIRONMENTS, PROJECT_CONFIG_VERSION, canCreateApps, defaultFlavour, isValidBundleId, } from "@capuchoo/core";
|
|
1
|
+
import { askText, confirm, isInteractive, log, selectOne } from "../cli/prompts.js";
|
|
2
|
+
import { ENVIRONMENTS, PROJECT_CONFIG_VERSION, canCreateApps, canPublishTo, defaultFlavour, environmentMismatchWarning, suggestEnvironment, isValidBundleId, } from "@capuchoo/core";
|
|
3
3
|
import { Flags } from "@oclif/core";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import fs from "node:fs";
|
|
@@ -10,13 +10,44 @@ import { projectConfigPath, readProjectConfig, resolveCredentials, writeProjectC
|
|
|
10
10
|
import AuthLogin from "./auth/login.js";
|
|
11
11
|
import { BaseCommand } from "../base-command.js";
|
|
12
12
|
export default class Init extends BaseCommand {
|
|
13
|
-
static description = "Link this directory to a
|
|
14
|
-
static examples = [
|
|
13
|
+
static description = "Link this directory to a Capuchoo app and write .capuchoo/project.json";
|
|
14
|
+
static examples = [
|
|
15
|
+
"<%= config.bin %> init",
|
|
16
|
+
"<%= config.bin %> init --link",
|
|
17
|
+
"<%= config.bin %> init --create",
|
|
18
|
+
'<%= config.bin %> init --create --name "My App" --app-id com.acme.app --channel staging',
|
|
19
|
+
];
|
|
15
20
|
static flags = {
|
|
16
21
|
link: Flags.boolean({
|
|
17
22
|
char: "l",
|
|
18
23
|
default: false,
|
|
19
|
-
description: "Link an existing app instead of
|
|
24
|
+
description: "Link an existing app instead of asking",
|
|
25
|
+
exclusive: ["create"],
|
|
26
|
+
}),
|
|
27
|
+
// Without this, a non-interactive shell could only ever link: the prompt
|
|
28
|
+
// named --link as its escape hatch and there was no flag for the other half
|
|
29
|
+
// of the same question.
|
|
30
|
+
create: Flags.boolean({
|
|
31
|
+
char: "c",
|
|
32
|
+
default: false,
|
|
33
|
+
description: "Create a new app instead of asking",
|
|
34
|
+
exclusive: ["link"],
|
|
35
|
+
}),
|
|
36
|
+
name: Flags.string({
|
|
37
|
+
description: "Name of the app to create (default: this directory's name)",
|
|
38
|
+
dependsOn: ["create"],
|
|
39
|
+
}),
|
|
40
|
+
// Used by both halves: with --create it is the identifier to register, with
|
|
41
|
+
// --link the identifier to select. Either way it names the app.
|
|
42
|
+
"app-id": Flags.string({
|
|
43
|
+
description: "Bundle identifier of the app, e.g. com.company.app",
|
|
44
|
+
}),
|
|
45
|
+
org: Flags.string({
|
|
46
|
+
description: "Organization to create the app in, by name or id",
|
|
47
|
+
dependsOn: ["create"],
|
|
48
|
+
}),
|
|
49
|
+
channel: Flags.string({
|
|
50
|
+
description: "Create this channel after linking, e.g. staging",
|
|
20
51
|
}),
|
|
21
52
|
force: Flags.boolean({
|
|
22
53
|
char: "f",
|
|
@@ -28,19 +59,16 @@ export default class Init extends BaseCommand {
|
|
|
28
59
|
const { flags } = await this.parse(Init);
|
|
29
60
|
const appDir = process.cwd();
|
|
30
61
|
this.log("");
|
|
31
|
-
this.log(chalk.bold(" Initialise
|
|
62
|
+
this.log(chalk.bold(" Initialise Capuchoo"));
|
|
32
63
|
this.log(chalk.dim(` ${appDir}`));
|
|
33
64
|
this.log("");
|
|
34
65
|
const existing = readProjectConfig(appDir);
|
|
35
66
|
if (existing && !flags.force) {
|
|
36
67
|
this.log(chalk.yellow(" Already initialised: ") + `${existing.appName} (${existing.appId})`);
|
|
37
|
-
const action = await
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
{ name: "Re-link to a different app", value: "relink" },
|
|
42
|
-
],
|
|
43
|
-
});
|
|
68
|
+
const action = await selectOne("What now?", [
|
|
69
|
+
{ value: "keep", label: "Keep it", hint: existing.appId },
|
|
70
|
+
{ value: "relink", label: "Re-link to a different app" },
|
|
71
|
+
], "--force");
|
|
44
72
|
if (action === "keep")
|
|
45
73
|
return;
|
|
46
74
|
}
|
|
@@ -67,14 +95,28 @@ export default class Init extends BaseCommand {
|
|
|
67
95
|
// --- pick or create the app ---------------------------------------------
|
|
68
96
|
const mode = flags.link
|
|
69
97
|
? "existing"
|
|
70
|
-
:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
{
|
|
74
|
-
{
|
|
75
|
-
],
|
|
98
|
+
: flags.create
|
|
99
|
+
? "new"
|
|
100
|
+
: await selectOne("Should this directory publish to an existing app, or a new one?", [
|
|
101
|
+
{ value: "existing", label: "An app that already exists" },
|
|
102
|
+
{ value: "new", label: "A new app" },
|
|
103
|
+
], "--link or --create");
|
|
104
|
+
const app = mode === "existing"
|
|
105
|
+
? await this.linkExisting(cloud, flags["app-id"])
|
|
106
|
+
: await this.createNew(cloud, {
|
|
107
|
+
name: flags.name,
|
|
108
|
+
appId: flags["app-id"],
|
|
109
|
+
org: flags.org,
|
|
76
110
|
});
|
|
77
|
-
|
|
111
|
+
// The key that just created or linked this app may not be allowed to publish
|
|
112
|
+
// to it: an app-scoped key can read every app the account owns. Say so here
|
|
113
|
+
// rather than let the first deploy discover it after a full build.
|
|
114
|
+
if (!canPublishTo(profile, app.id)) {
|
|
115
|
+
this.log("");
|
|
116
|
+
this.log(chalk.yellow(" The API key in use is restricted to another app, so deploys from here\n" +
|
|
117
|
+
` will be refused. Run "capuchoo auth login" with a key for ${app.name}\n` +
|
|
118
|
+
" or an unscoped one."));
|
|
119
|
+
}
|
|
78
120
|
// --- flavours ------------------------------------------------------------
|
|
79
121
|
const flavours = this.detectFlavours(appDir);
|
|
80
122
|
const detected = Object.keys(flavours);
|
|
@@ -127,12 +169,55 @@ export default class Init extends BaseCommand {
|
|
|
127
169
|
chalk.cyan(`capuchoo deploy ota --channel ${deployable[0].name}`));
|
|
128
170
|
}
|
|
129
171
|
else {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
172
|
+
// An app with no channel is not deployable, and init already knows it.
|
|
173
|
+
// Sending people to the dashboard here was the one thing that made setting
|
|
174
|
+
// an app up a CLI-browser-CLI round trip.
|
|
175
|
+
await this.offerFirstChannel(cloud, app, flags.channel);
|
|
133
176
|
}
|
|
134
177
|
this.log("");
|
|
135
178
|
}
|
|
179
|
+
/**
|
|
180
|
+
* Creates the first channel, because a linked app without one cannot be
|
|
181
|
+
* deployed to. The environment is chosen explicitly: a channel's name never
|
|
182
|
+
* decides which flavour it serves.
|
|
183
|
+
*/
|
|
184
|
+
async offerFirstChannel(cloud, app, requested) {
|
|
185
|
+
this.log(chalk.yellow(" This app has no channel yet, so nothing can be deployed to it."));
|
|
186
|
+
this.log(chalk.dim(" A channel's environment is what tells the CLI which flavour to build.\n"));
|
|
187
|
+
const name = requested?.trim();
|
|
188
|
+
if (!name && !isInteractive()) {
|
|
189
|
+
this.log(chalk.dim(" Create one with: ") +
|
|
190
|
+
chalk.cyan("capuchoo channel create staging") +
|
|
191
|
+
chalk.dim(" (or pass --channel)"));
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (!name) {
|
|
195
|
+
const create = await confirm("Create one now?", { default: true });
|
|
196
|
+
if (!create) {
|
|
197
|
+
this.log(chalk.dim(" Later: ") + chalk.cyan("capuchoo channel create staging"));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const chosen = name ?? (await askText("Channel name", { initial: "staging", flag: "--channel" })).trim();
|
|
202
|
+
const environment = suggestEnvironment(chosen) ??
|
|
203
|
+
(await selectOne(`Which flavour should "${chosen}" serve?`, ENVIRONMENTS.map((value) => ({ value, label: value })), "--channel <name> with a name like staging"));
|
|
204
|
+
const warning = environmentMismatchWarning(chosen, environment);
|
|
205
|
+
if (warning)
|
|
206
|
+
log.warn(warning);
|
|
207
|
+
try {
|
|
208
|
+
const channel = await cloud.createChannel({ app_id: app.id, name: chosen, environment });
|
|
209
|
+
this.log("");
|
|
210
|
+
this.log(` ${chalk.green("Created channel")} ${channel.name} ${chalk.dim(`(${environment})`)}`);
|
|
211
|
+
this.log(chalk.dim(" Deploy with: ") + chalk.cyan(`capuchoo deploy ota --channel ${channel.name}`));
|
|
212
|
+
}
|
|
213
|
+
catch (error) {
|
|
214
|
+
// The app and project.json are already correct, so this is a warning and
|
|
215
|
+
// not a failure - `capuchoo channel create` retries just this step.
|
|
216
|
+
this.log("");
|
|
217
|
+
this.log(chalk.yellow(` The channel could not be created: ${error instanceof Error ? error.message : String(error)}`));
|
|
218
|
+
this.log(chalk.dim(` Retry with: capuchoo channel create ${chosen}`));
|
|
219
|
+
}
|
|
220
|
+
}
|
|
136
221
|
/** Detects which flavours the project actually has files for. */
|
|
137
222
|
detectFlavours(appDir) {
|
|
138
223
|
const found = {};
|
|
@@ -156,23 +241,24 @@ export default class Init extends BaseCommand {
|
|
|
156
241
|
}
|
|
157
242
|
return "dist";
|
|
158
243
|
}
|
|
159
|
-
async linkExisting(cloud) {
|
|
244
|
+
async linkExisting(cloud, wanted) {
|
|
160
245
|
const spinner = ora({ text: "Fetching apps", stream: process.stderr }).start();
|
|
161
246
|
const apps = await cloud.apps();
|
|
162
247
|
spinner.stop();
|
|
163
248
|
if (apps.length === 0) {
|
|
164
|
-
this.error("This account has no apps yet.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
249
|
+
this.error("This account has no apps yet. Run capuchoo init --create to register one.");
|
|
250
|
+
}
|
|
251
|
+
if (wanted) {
|
|
252
|
+
const match = apps.find((app) => app.app_id === wanted || app.id === wanted);
|
|
253
|
+
if (!match) {
|
|
254
|
+
this.error(`No app called "${wanted}" on this account. ` +
|
|
255
|
+
`Available: ${apps.map((app) => app.app_id).join(", ")}.`);
|
|
256
|
+
}
|
|
257
|
+
return match;
|
|
258
|
+
}
|
|
259
|
+
return selectOne("Which app does this directory publish to?", apps.map((app) => ({ value: app, label: app.name, hint: app.app_id })), "--app-id");
|
|
174
260
|
}
|
|
175
|
-
async createNew(cloud) {
|
|
261
|
+
async createNew(cloud, given) {
|
|
176
262
|
const spinner = ora({
|
|
177
263
|
text: "Fetching organizations",
|
|
178
264
|
stream: process.stderr,
|
|
@@ -186,30 +272,37 @@ export default class Init extends BaseCommand {
|
|
|
186
272
|
: "This account is a member, not an owner or admin, of every organization " +
|
|
187
273
|
"it belongs to, so it cannot create an app. Ask an owner.");
|
|
188
274
|
}
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
275
|
+
const requested = given.org?.trim().toLowerCase();
|
|
276
|
+
const chosen = requested
|
|
277
|
+
? allowed.find((org) => org.id === given.org?.trim() || org.name.toLowerCase() === requested)
|
|
278
|
+
: undefined;
|
|
279
|
+
if (requested && !chosen) {
|
|
280
|
+
this.error(`No organization called "${given.org}" that this account can create apps in. ` +
|
|
281
|
+
`Available: ${allowed.map((org) => org.name).join(", ")}.`);
|
|
282
|
+
}
|
|
283
|
+
const organizationId = chosen?.id ??
|
|
284
|
+
(allowed.length === 1
|
|
285
|
+
? allowed[0].id
|
|
286
|
+
: await selectOne("Organization", allowed.map((org) => ({ value: org.id, label: org.name, hint: org.role })), "--org"));
|
|
287
|
+
const name = given.name?.trim() ||
|
|
288
|
+
(await askText("App name", {
|
|
289
|
+
initial: path.basename(process.cwd()),
|
|
290
|
+
flag: "--name",
|
|
291
|
+
}));
|
|
292
|
+
const appId = given.appId?.trim() ||
|
|
293
|
+
(await askText("Production bundle identifier", {
|
|
294
|
+
placeholder: "com.company.app",
|
|
295
|
+
flag: "--app-id",
|
|
296
|
+
validate: (value) => isValidBundleId(value.trim())
|
|
297
|
+
? undefined
|
|
298
|
+
: "Expected something like com.company.app - lower case, at least two segments",
|
|
299
|
+
}));
|
|
300
|
+
if (!isValidBundleId(appId)) {
|
|
301
|
+
this.error(`"${appId}" is not a bundle identifier. Expected something like com.company.app - ` +
|
|
302
|
+
"lower case, at least two segments.");
|
|
303
|
+
}
|
|
208
304
|
// Creating an app is the one irreversible thing this command does.
|
|
209
|
-
const proceed = await confirm({
|
|
210
|
-
message: `Create "${name}" (${appId})?`,
|
|
211
|
-
default: true,
|
|
212
|
-
});
|
|
305
|
+
const proceed = await confirm(`Create "${name}" (${appId})?`, { default: true });
|
|
213
306
|
if (!proceed)
|
|
214
307
|
this.error("Cancelled.");
|
|
215
308
|
const creating = ora({ text: "Creating app", stream: process.stderr }).start();
|