@isomorph.ai/cli 0.3.4-rc.1 → 0.4.0-rc.1
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 +12 -14
- package/dist/packages/harbour-cli/src/agent-setup.js +25 -125
- package/dist/packages/harbour-cli/src/auth.js +1 -1
- package/dist/packages/harbour-cli/src/check.js +7 -34
- package/dist/packages/harbour-cli/src/cli.js +46 -100
- package/dist/packages/harbour-cli/src/config.js +51 -3
- package/dist/packages/harbour-cli/src/{productionise.js → deploy.js} +130 -183
- package/dist/packages/harbour-cli/src/dev.js +81 -2
- package/dist/packages/harbour-cli/src/guide.js +31 -45
- package/dist/packages/harbour-cli/src/integrations.js +47 -74
- package/dist/packages/harbour-cli/src/kit-bundle.js +0 -3
- package/dist/packages/harbour-cli/src/kit-bundle.manifest.js +9 -9
- package/dist/packages/harbour-cli/src/kit.js +43 -16
- package/dist/packages/harbour-cli/src/local-runtime.js +23 -1
- package/dist/packages/harbour-cli/src/operations.js +11 -90
- package/dist/packages/harbour-cli/src/output.js +24 -41
- package/dist/packages/harbour-cli/src/remote-mcp-client.js +4 -4
- package/dist/packages/harbour-cli/src/starter.js +21 -201
- package/dist/packages/harbour-cli/src/upload.js +1 -1
- package/dist/packages/harbour-cli/src/version.js +1 -1
- package/dist/src/analyzer.js +1 -1
- package/dist/src/secret-paths.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npx -y @isomorph.ai/cli agent-setup
|
|
|
12
12
|
|
|
13
13
|
Paste that line into Claude Code or Codex (or a terminal). It teaches both agents the kit by installing the `isomorph` skill for each (`~/.claude/skills/isomorph/SKILL.md`, `~/.codex/skills/isomorph/SKILL.md`, with the kit's rules beside it as `core.md`, `integrations.md`, `ai.md` and `jobs.md`) and never touches your other skills or instructions. A skill is read only when the task matches it — a folder with `.isomorph/`, an app you ask for on Isomorph — so the agent works exactly as before on everything else. (Releases before 0.1.34 put the guide in your global Codex instructions, `~/.codex/AGENTS.md`, where every Codex session read it; running the line again takes that block out and leaves the rest of the file as it was.)
|
|
14
14
|
|
|
15
|
-
That `npx` line runs the current release
|
|
15
|
+
That `npx` line runs the current release; `isomorph init`, `dev`, `check` and `deploy` afterwards run whatever `isomorph` is installed on this machine. A CLI older than what your company's Isomorph platform accepts is refused by every company command in under a second (`CLI_UPGRADE_REQUIRED`), with the fix: `npm i -g @isomorph.ai/cli`.
|
|
16
16
|
|
|
17
17
|
Then, in an empty folder:
|
|
18
18
|
|
|
@@ -27,28 +27,26 @@ The only step you do yourself is the company sign-in: when the agent runs `isomo
|
|
|
27
27
|
## Commands
|
|
28
28
|
|
|
29
29
|
```
|
|
30
|
-
isomorph agent-setup install the `isomorph` skill for Claude Code and Codex; idempotent
|
|
31
|
-
isomorph init --app-root <path> [--upgrade] starter app in an empty folder, or kit files in a Vite + React app (also runs agent-setup); --upgrade re-pins the kit bundle
|
|
32
|
-
isomorph dev --app-root <path> [--reset]
|
|
30
|
+
isomorph agent-setup install the `isomorph` skill for Claude Code and Codex; idempotent
|
|
31
|
+
isomorph init --app-root <path> [--upgrade] starter app in an empty folder, or kit files in a Vite + React app (also runs agent-setup); --upgrade re-pins the kit bundle and runs the checks
|
|
32
|
+
isomorph dev --app-root <path> [--reset] [--detach] [--json] run the app locally on one loopback origin; --detach starts it in the background and prints {origin, pid} once it answers
|
|
33
33
|
isomorph stop --app-root <path> stop local services, keep data
|
|
34
|
-
isomorph check --app-root <path> [--integrations] [--json]
|
|
34
|
+
isomorph check --app-root <path> [--integrations] [--json] types, build, migrations, database gate, write probe, journeys, coverage; --json prints the whole report
|
|
35
35
|
isomorph integrations catalog --app-root <path> [--json] the company's connections as .isomorph/integrations.json names them, with approved channels/views per environment
|
|
36
|
-
isomorph integrations request <connection> --app-root <path> [--reason <text>] one request per connection; IT approves it once for every environment (`isomorph dev` and `isomorph
|
|
36
|
+
isomorph integrations request <connection> --app-root <path> [--reason <text>] one request per connection; IT approves it once for every environment (`isomorph dev` and `isomorph deploy` file it for you)
|
|
37
37
|
isomorph integrations status --app-root <path> [--json]
|
|
38
38
|
isomorph connect <work-email | company-start-url> once per company; then isomorph login | logout
|
|
39
|
-
isomorph
|
|
40
|
-
isomorph status | retry | promote
|
|
39
|
+
isomorph deploy --app-root <path> [--json] checks (when not already passed for this code), one pre-flight, save + preview deployment; prints the protected link
|
|
40
|
+
isomorph status | retry | promote --operation <reference> (promote: [--app-root <path>] [--confirm-tested])
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`
|
|
43
|
+
`deploy` reads the app's name, description and audience from `.isomorph/app.json` (written by `init`; edit it before shipping) and prints the three values before it starts. It then asks Isomorph once whether the app can deploy — the access request for every declared connection, the company's AI setup when the app calls governed AI, and this CLI's version — and refuses with exit 2 naming every blocker and its fix (`DEPLOY_BLOCKED`, or `INTEGRATIONS_NOT_READY` / `AI_NOT_READY` / `CLI_UPGRADE_REQUIRED` when the blockers are all of one kind).
|
|
44
44
|
|
|
45
|
-
`
|
|
45
|
+
`deploy`, `status --wait`, `retry` and `promote` follow the deployment for up to `--max-wait <seconds>` (default 30 minutes). Past that they exit 0 with `status: "RUNNING"` and the `isomorph status --operation <reference> --wait --max-wait 120 --json` that continues the same deployment — a bounded wait is not a failure, and never a reason to start another deploy.
|
|
46
46
|
|
|
47
|
-
`
|
|
48
|
-
|
|
49
|
-
## Apps set up by the old `harbour` CLI
|
|
47
|
+
`promote` never prompts: `--confirm-tested` is the one flag that means the person has opened the preview and it works. `productionise` is still accepted as an unlisted alias of `deploy` for one release.
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
`isomorph --help` prints the full usage. Local commands need no company sign-in; integrations and shipping do.
|
|
52
50
|
|
|
53
51
|
## Development
|
|
54
52
|
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import { execFile } from "node:child_process";
|
|
2
1
|
import { mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
2
|
import { homedir } from "node:os";
|
|
4
3
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
import { promisify } from "node:util";
|
|
7
|
-
import { CLI_VERSION } from "./version.js";
|
|
8
4
|
import { GUIDE_MODULES } from "./guide.js";
|
|
9
5
|
import { continueCommand } from "./operations.js";
|
|
10
|
-
const execFileAsync = promisify(execFile);
|
|
11
6
|
export const MANAGED_START = "<!-- isomorph:kit:start -->";
|
|
12
7
|
export const MANAGED_END = "<!-- isomorph:kit:end -->";
|
|
13
|
-
/**
|
|
14
|
-
* The one command that installs or upgrades the CLI. It carries no dist-tag: npm resolves an
|
|
15
|
-
* untagged name through `latest`, which is what a release publishes, so every instruction in
|
|
16
|
-
* the kit names the package the same way a person typing it from memory would (LLD §15.5).
|
|
17
|
-
*/
|
|
18
|
-
export const CLI_INSTALL_COMMAND = "npm i -g @isomorph.ai/cli";
|
|
19
8
|
/**
|
|
20
9
|
* Where the two agents read the user-level guide from; both honour the tools' own
|
|
21
10
|
* override variables. Both are skills — a file each tool lists by name and description
|
|
@@ -40,13 +29,11 @@ export function agentPaths(env = process.env) {
|
|
|
40
29
|
* workflow) and the four rule modules beside it, every file wholly owned by the kit —
|
|
41
30
|
* and takes the guide back out of Codex's global AGENTS.md where an older release put
|
|
42
31
|
* it, keeping everything else in that file. Idempotent:
|
|
43
|
-
* unchanged files are reported as kept.
|
|
44
|
-
* command will run (`result.cli`), because this command is routinely reached through
|
|
45
|
-
* `npx` while everything after it is not.
|
|
32
|
+
* unchanged files are reported as kept.
|
|
46
33
|
*/
|
|
47
|
-
export async function agentSetup(env = process.env
|
|
34
|
+
export async function agentSetup(env = process.env) {
|
|
48
35
|
const paths = agentPaths(env);
|
|
49
|
-
const result = { created: [], updated: [], kept: [], removed: []
|
|
36
|
+
const result = { created: [], updated: [], kept: [], removed: [] };
|
|
50
37
|
for (const skill of [paths.claudeSkill, paths.codexSkill])
|
|
51
38
|
for (const file of skillFiles(skill))
|
|
52
39
|
result[await upsertManagedBlock(file.path, file.text, {})].push(file.path);
|
|
@@ -58,94 +45,6 @@ export async function agentSetup(env = process.env, lookups = {}) {
|
|
|
58
45
|
export function skillFiles(skill) {
|
|
59
46
|
return [{ path: skill, text: `${SKILL_FRONTMATTER}\n${AGENT_GUIDE}` }, ...Object.entries(GUIDE_MODULES).map(([name, text]) => ({ path: join(dirname(skill), `${name}.md`), text }))];
|
|
60
47
|
}
|
|
61
|
-
/** Compares the globally resolvable `isomorph` with the package this process runs from. */
|
|
62
|
-
export async function checkCliVersion(lookups = {}) {
|
|
63
|
-
const running = await (lookups.runningCliVersion ?? runningPackageVersion)();
|
|
64
|
-
// No `isomorph` on PATH, output with no version in it, a non-zero exit or a hang all
|
|
65
|
-
// mean the same thing: there is no installed CLI whose version can be trusted.
|
|
66
|
-
const installed = parseVersion(await (lookups.installedCliVersion ?? installedCliVersion)() ?? "");
|
|
67
|
-
const consequence = "carries an old kit bundle, so the app it creates passes every local check and is then refused by the deployment pipeline (kit_bundle_incompatible)";
|
|
68
|
-
if (!installed) {
|
|
69
|
-
// The CLI used to be published under another name with another command; a machine that still has it needs to know that it no longer deploys.
|
|
70
|
-
const legacy = await (lookups.legacyCliInstalled ?? legacyCliInstalled)() ? " The old `harbour` CLI is installed; it no longer deploys — install @isomorph.ai/cli." : "";
|
|
71
|
-
return { running, state: "missing", upgradeRequired: true, remediation: CLI_INSTALL_COMMAND, message: `No \`isomorph\` command is installed on this machine; this package is ${running}. \`isomorph init\`, \`dev\` and \`check\` run the installed CLI, not this one, and a missing or stale CLI ${consequence}.${legacy}` };
|
|
72
|
-
}
|
|
73
|
-
const order = compareCliVersions(installed, running);
|
|
74
|
-
if (order < 0)
|
|
75
|
-
return { running, installed, state: "stale", upgradeRequired: true, remediation: CLI_INSTALL_COMMAND, message: `The installed \`isomorph\` command is ${installed}, older than this package (${running}). \`isomorph init\`, \`dev\` and \`check\` run the installed CLI, not this one, and a stale CLI ${consequence}.` };
|
|
76
|
-
if (order > 0)
|
|
77
|
-
return { running, installed, state: "ahead", upgradeRequired: false, message: `The installed \`isomorph\` command is ${installed}, newer than this package (${running}); the installed one is what runs.` };
|
|
78
|
-
return { running, installed, state: "current", upgradeRequired: false, message: `The installed \`isomorph\` command is ${installed}, the same version as this package.` };
|
|
79
|
-
}
|
|
80
|
-
/** What `agent-setup` and `init` print about the installed CLI; an upgrade is impossible to miss in a scrolling log. */
|
|
81
|
-
export function cliVersionLines(check) {
|
|
82
|
-
if (!check.upgradeRequired)
|
|
83
|
-
return [check.message];
|
|
84
|
-
return ["", "!!! UPGRADE THE ISOMORPH CLI BEFORE `isomorph init` OR ANY OTHER ISOMORPH COMMAND !!!", check.message, `Run: ${check.remediation}`, ""];
|
|
85
|
-
}
|
|
86
|
-
/** Numeric-core semver order; a prerelease sorts before its release. An unparseable version compares equal, so nothing is called stale on a guess. */
|
|
87
|
-
export function compareCliVersions(left, right) {
|
|
88
|
-
const parse = (value) => {
|
|
89
|
-
const match = /^\s*v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?/.exec(value);
|
|
90
|
-
return match ? { core: [Number(match[1]), Number(match[2]), Number(match[3])], pre: match[4] } : undefined;
|
|
91
|
-
};
|
|
92
|
-
const a = parse(left);
|
|
93
|
-
const b = parse(right);
|
|
94
|
-
if (!a || !b)
|
|
95
|
-
return 0;
|
|
96
|
-
for (let index = 0; index < 3; index += 1)
|
|
97
|
-
if (a.core[index] !== b.core[index])
|
|
98
|
-
return a.core[index] < b.core[index] ? -1 : 1;
|
|
99
|
-
if (a.pre === b.pre)
|
|
100
|
-
return 0;
|
|
101
|
-
if (a.pre === undefined)
|
|
102
|
-
return 1;
|
|
103
|
-
if (b.pre === undefined)
|
|
104
|
-
return -1;
|
|
105
|
-
return a.pre < b.pre ? -1 : 1;
|
|
106
|
-
}
|
|
107
|
-
/** Whatever the globally resolvable `isomorph` prints for `--version`; undefined when running it is not possible at all. */
|
|
108
|
-
async function installedCliVersion() {
|
|
109
|
-
try {
|
|
110
|
-
const { stdout } = await execFileAsync("isomorph", ["--version"], { timeout: 20_000, windowsHide: true });
|
|
111
|
-
return stdout;
|
|
112
|
-
}
|
|
113
|
-
catch (error) {
|
|
114
|
-
return error.stdout;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
/** Whether the CLI's previous command name is still on PATH (whatever it answers). */
|
|
118
|
-
async function legacyCliInstalled() {
|
|
119
|
-
try {
|
|
120
|
-
await execFileAsync("harbour", ["--version"], { timeout: 20_000, windowsHide: true });
|
|
121
|
-
return true;
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
return error.code !== "ENOENT";
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function parseVersion(text) {
|
|
128
|
-
return /(\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?)/.exec(text.trim())?.[1];
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* The version of the package this process runs from: the nearest package.json named
|
|
132
|
-
* `@isomorph.ai/cli`, walking up from this file. That is `packages/harbour-cli/`
|
|
133
|
-
* in the repository and the package root once published (the entry point is
|
|
134
|
-
* `dist/packages/harbour-cli/src/cli.js`), so no environment variable is involved.
|
|
135
|
-
*/
|
|
136
|
-
async function runningPackageVersion() {
|
|
137
|
-
let directory = dirname(fileURLToPath(import.meta.url));
|
|
138
|
-
for (let depth = 0; depth < 12; depth += 1) {
|
|
139
|
-
const manifest = await readFile(join(directory, "package.json"), "utf8").then(text => JSON.parse(text), () => undefined);
|
|
140
|
-
if (manifest?.name === "@isomorph.ai/cli" && typeof manifest.version === "string")
|
|
141
|
-
return manifest.version;
|
|
142
|
-
const parent = dirname(directory);
|
|
143
|
-
if (parent === directory)
|
|
144
|
-
break;
|
|
145
|
-
directory = parent;
|
|
146
|
-
}
|
|
147
|
-
return CLI_VERSION;
|
|
148
|
-
}
|
|
149
48
|
/**
|
|
150
49
|
* Writes `block` to `file`: creates the file, replaces the text between the markers
|
|
151
50
|
* when both are present, or appends the block after the existing content. Without
|
|
@@ -201,34 +100,35 @@ description: ${SKILL_DESCRIPTION}
|
|
|
201
100
|
/** One guide, shared by the Claude Code and Codex skills: the workflow only, written for an agent working with a non-developer. Every rule lives in a module (`guide.ts`) beside it and is pointed at, not restated. */
|
|
202
101
|
export const AGENT_GUIDE = `# Isomorph development kit
|
|
203
102
|
|
|
204
|
-
This guide is for one job: an app that runs on Isomorph
|
|
103
|
+
This guide is for one job: an app that runs on Isomorph (a folder with \`.isomorph/\`, or a new app asked for on Isomorph). Anything else: ignore every rule below and work as you always do. Never turn an existing project into an Isomorph app unless asked by name.
|
|
205
104
|
|
|
206
|
-
The person may not be a developer
|
|
105
|
+
The person may not be a developer: they say what they want in plain English; you build it and run every command (they type none but the sign-in below). Use \`--json\`; never paste JSON, logs or file contents at them.
|
|
207
106
|
|
|
208
|
-
|
|
107
|
+
Rules live beside this file: \`core.md\` before the first edit; \`integrations.md\`, \`ai.md\`, \`jobs.md\` before the work each names.
|
|
209
108
|
|
|
210
|
-
## Getting ready
|
|
109
|
+
## Getting ready
|
|
211
110
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
3. Sign-in, needed only for company systems and shipping: run \`isomorph login\`. It opens the browser and the person finishes the sign-in there — the one step they do themselves; tell them so in one line. It then prints the company and what IT has set up. If it says the company is not connected yet, ask for their work email and run \`isomorph connect <work-email>\` — Isomorph looks the company up from the email's domain. Only if that fails use a link: if it answers that more than one company admits the domain, ask which one and run the command it printed; if it says no company admits the domain, their IT admin has to add it or give them a link (their Isomorph page \`https://platform.isomorph.ai/t/<company>\`, or the setup link), and then run \`isomorph connect <link>\`.
|
|
111
|
+
- No \`.isomorph/\` yet: \`isomorph init --app-root .\` (empty folder → starter; Vite + React app → kit files only). Node 22+, no Docker.
|
|
112
|
+
- Sign-in (company systems and shipping only): \`isomorph connect <work-email>\` (refused? run the command it prints), then \`isomorph login\` — the browser opens and the person finishes there (their one step; say so).
|
|
215
113
|
|
|
216
|
-
##
|
|
114
|
+
## Intent → command
|
|
217
115
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
116
|
+
| They say | You do |
|
|
117
|
+
|---|---|
|
|
118
|
+
| "run it", "show me" | \`isomorph dev --app-root . --detach --json\`; give them \`result.origin\` (the first start takes a minute or two). Locally they are a fixture user. |
|
|
119
|
+
| "check it", "is it ready?" | \`isomorph check --app-root . --json\` (the output is the report). Failures in the app's code are yours: fix and re-check until clean, after every change and before every ship, unasked and never offered as a choice. |
|
|
120
|
+
| "does it work?" (and before reporting anything as working) | open the dev link in your own browser, press the control you built or changed, read what the app shows; a green \`isomorph check\` is not that proof (fixtures answer AI and company systems). A Send in development is real: reuse explicit authorization or ask once. No browser: say the button is untested. |
|
|
121
|
+
| "I need Slack / Gmail / the warehouse" | read \`integrations.md\` beside this skill first; then, in the same turn: catalog, declare only what the app calls, submit the request yourself, say READY or PENDING in one line. |
|
|
122
|
+
| "summarise", "draft", "AI" | read \`ai.md\` beside this skill first; then one call behind a control they press. |
|
|
123
|
+
| "ship it", "let my team try it" | show the person the three values in \`.isomorph/app.json\` (name, description, audience — "only you" when empty) and correct the file as they say; then \`isomorph deploy --app-root . --json\` (it runs the checks when needed and refuses once naming every blocker and its fix: relay that line; the app works meanwhile). Give them \`result.deployment.protectedUrl\`. \`--session-notes\`: what cost you time in Isomorph, or \`""\`. |
|
|
124
|
+
| "make it live" | only after they have tried the preview: \`isomorph promote --operation <ref> --app-root . --confirm-tested --json\`. Report the production link, or that operator approval is pending. |
|
|
125
|
+
| "stop it" | \`isomorph stop --app-root .\` (data kept); \`isomorph dev --app-root . --reset\` only when they ask to start over. |
|
|
126
|
+
| "every day at 2pm", "send this automatically" | read \`jobs.md\` beside this skill first; then write the job, run it once with \`isomorph jobs run\`, and say the sentence it gives you. |
|
|
227
127
|
|
|
228
|
-
|
|
128
|
+
A refusal names its layer, its reason and its fix: change that one thing, then run it again. A wait timeout is not a failure — work is still running: run the command it prints (\`${continueCommand("<ref>")}\`); never start a second deploy while one is running.
|
|
229
129
|
|
|
230
130
|
## Talking to the person
|
|
231
131
|
|
|
232
|
-
- Plain words, short: "
|
|
233
|
-
-
|
|
234
|
-
- End a turn with at most one question,
|
|
132
|
+
- Plain words, short: "IT has to approve Slack; the app works meanwhile." Say what happens next and how long; report only what you observed; say when something is unknown.
|
|
133
|
+
- Reports: brief and natural — what you verified, any remaining blocker and who resolves it, any material scope decision; no forced headings or repeated status.
|
|
134
|
+
- End a turn with at most one question, only when a decision is genuinely theirs and you cannot go on without it. Never offer to do something this guide already tells you to do unasked — do it and report.`;
|
|
@@ -83,7 +83,7 @@ const REFRESH_MARGIN_MS = 5 * 60_000;
|
|
|
83
83
|
/**
|
|
84
84
|
* Returns a usable access token, refreshing only when the stored one is about
|
|
85
85
|
* to expire. Refresh tokens are single-use, so every CLI process refreshing on
|
|
86
|
-
* start meant two processes at once (a `
|
|
86
|
+
* start meant two processes at once (a `deploy` still polling and a
|
|
87
87
|
* `status` beside it) spent the same refresh token and one was told to sign in
|
|
88
88
|
* again. A refresh that fails now re-reads the store once: if a sibling
|
|
89
89
|
* process rotated the pair in the meantime, its newer token is used instead.
|
|
@@ -5,16 +5,17 @@ import { readAppSchema } from "./app-schema.js";
|
|
|
5
5
|
import { describeRetainedChecks, syncRetainedChecks } from "./retained-checks.js";
|
|
6
6
|
import { CliError } from "./output.js";
|
|
7
7
|
import { DEPENDENT_READ_OPERATIONS, READ_OPERATIONS, kitPaths, readDeclaration, readKitLock, resourceNames, sourceDigest } from "./kit.js";
|
|
8
|
-
import { LocalRuntime, allocatePorts, ensureSdk, freePort, nodePackageCommand, readDevLock,
|
|
8
|
+
import { LocalRuntime, allocatePorts, ensureSdk, freePort, nodePackageCommand, readDevLock, runningOrigin } from "./local-runtime.js";
|
|
9
9
|
import { CLI_VERSION } from "./version.js";
|
|
10
10
|
/**
|
|
11
11
|
* What actually failed, in the refusal itself.
|
|
12
12
|
*
|
|
13
13
|
* The report is already in hand at the call site, and a refusal that spends its
|
|
14
14
|
* message on a file path costs the reader a whole turn to learn anything —
|
|
15
|
-
* `
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* `deploy` already names the failing checks when it refuses to deploy
|
|
16
|
+
* and `check` did not. Every failing check is named, in full — names are never
|
|
17
|
+
* elided — and the detail of the first failure is carried too, capped, because
|
|
18
|
+
* "which check" is a smaller answer than "why".
|
|
18
19
|
*/
|
|
19
20
|
export function checksFailedMessage(report) {
|
|
20
21
|
const failed = report.checks.filter(check => check.status === "fail");
|
|
@@ -93,7 +94,7 @@ export async function runChecks(root, options) {
|
|
|
93
94
|
// After the gate, because `.isomorph/checks/*.mjs` is part of the deployed
|
|
94
95
|
// tree (kit.ts `sourceDigest`) and the gate may have regenerated it: the
|
|
95
96
|
// report has to pin the tree the journeys actually ran against, or
|
|
96
|
-
// `isomorph
|
|
97
|
+
// `isomorph deploy` would refuse the very tree this check just passed
|
|
97
98
|
// as CHECKS_STALE.
|
|
98
99
|
const source = await sourceDigest(root);
|
|
99
100
|
const report = {
|
|
@@ -101,7 +102,7 @@ export async function runChecks(root, options) {
|
|
|
101
102
|
createdAt: new Date().toISOString(),
|
|
102
103
|
sourceDigest: source.digest,
|
|
103
104
|
sourceFiles: source.entries,
|
|
104
|
-
toolchain: { node: process.version, cliVersion: CLI_VERSION, bundle: { kitVersion: bundle.kitVersion, sdkTarballSha256: bundle.sdk.tarballSha256,
|
|
105
|
+
toolchain: { node: process.version, cliVersion: CLI_VERSION, bundle: { kitVersion: bundle.kitVersion, sdkTarballSha256: bundle.sdk.tarballSha256, briefFingerprint: bundle.brief.fingerprint } },
|
|
105
106
|
checks,
|
|
106
107
|
gate,
|
|
107
108
|
integrations,
|
|
@@ -255,34 +256,6 @@ async function ensureSession(root, runtime, bundle, output) {
|
|
|
255
256
|
await runtime.sessionEnv();
|
|
256
257
|
return true;
|
|
257
258
|
}
|
|
258
|
-
/**
|
|
259
|
-
* `productionise` pre-flight for a kit app: the same gate the pipeline runs,
|
|
260
|
-
* here, before any operation exists. A failed check is refused with the
|
|
261
|
-
* pipeline's own wording (kit.check-failed: <check>); a local runtime that
|
|
262
|
-
* cannot start is reported and skipped — the pipeline's gate
|
|
263
|
-
* still runs.
|
|
264
|
-
*/
|
|
265
|
-
export async function preflightKitGate(root, bundle, output, run = runCommand, fetchImpl, runtime) {
|
|
266
|
-
const lock = await readKitLock(root).catch(() => undefined);
|
|
267
|
-
if (!lock)
|
|
268
|
-
return "skipped";
|
|
269
|
-
let report;
|
|
270
|
-
try {
|
|
271
|
-
report = await runKitGate(root, { run, bundle, output, ...(fetchImpl ? { fetch: fetchImpl } : {}), ...(runtime ? { runtime } : {}) });
|
|
272
|
-
}
|
|
273
|
-
catch (error) {
|
|
274
|
-
if (error instanceof CliError && ["LOCAL_RUNTIME_FAILED", "KIT_IMAGES_UNAVAILABLE", "KIT_RUNTIME_UNAVAILABLE"].includes(error.code)) {
|
|
275
|
-
output(`The kit gate was not run locally (${error.message}); the pipeline's gate will run in CodeBuild.`);
|
|
276
|
-
return "skipped";
|
|
277
|
-
}
|
|
278
|
-
throw error;
|
|
279
|
-
}
|
|
280
|
-
const failed = report.checks.filter(check => check.status === "fail");
|
|
281
|
-
if (failed.length)
|
|
282
|
-
throw new CliError("KIT_GATE_FAILED", `The kit gate refused this app (${failed.length} check(s) failed):\n${failed.map(check => ` - ${check.name}: ${check.detail ?? ""}`).join("\n")}\nFix the app and run \`isomorph check\`; the pipeline would refuse this deployment as kit.check-failed: ${failed[0].name}.`);
|
|
283
|
-
output(`The kit gate passed locally (${report.checks.length} checks; ${report.inventory.journeys.length} journey(s), ${report.inventory.migrations} migration(s)).`);
|
|
284
|
-
return "passed";
|
|
285
|
-
}
|
|
286
259
|
// ---- Real integrations ----------------------------------------------------------------
|
|
287
260
|
/** Explicit read operations only (READ_OPERATIONS) against READY development grants; sends never run. A dependent read (gmail.message.read) is exercised through its list: the newest message of the first thread, when there is one. */
|
|
288
261
|
async function testIntegrationReads(appId, client, declaration, output) {
|