@isomorph.ai/cli 0.7.0 → 0.7.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/dist/packages/harbour-cli/src/cli.js +4 -2
- package/dist/packages/harbour-cli/src/deploy.js +2 -1
- package/dist/packages/harbour-cli/src/dev.js +6 -1
- package/dist/packages/harbour-cli/src/guide.js +2 -2
- package/dist/packages/harbour-cli/src/kit-bundle.manifest.js +9 -9
- package/dist/packages/harbour-cli/src/kit.js +36 -3
- package/dist/packages/harbour-cli/src/local-runtime.js +15 -0
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ import { CLI_VERSION } from "./version.js";
|
|
|
6
6
|
import { connectedAccount, login, logout, refreshStoredToken } from "./auth.js";
|
|
7
7
|
import { assertCliCurrent, companyLabel, connect, loadConfig, resolveConfig } from "./config.js";
|
|
8
8
|
import { EMBEDDED_KIT_BUNDLE } from "./kit-bundle.js";
|
|
9
|
-
import { appRoot, readAppProfile, readKitLock, requestResourceName } from "./kit.js";
|
|
9
|
+
import { appRoot, assertLockCompany, readAppProfile, readKitLock, requestResourceName } from "./kit.js";
|
|
10
10
|
import { initKit } from "./starter.js";
|
|
11
11
|
import { packageApp } from "./package.js";
|
|
12
12
|
import { agentPaths, agentSetup } from "./agent-setup.js";
|
|
@@ -252,7 +252,9 @@ else {
|
|
|
252
252
|
// from the folder (the current one unless --app-root says otherwise) and,
|
|
253
253
|
// without --operation, follow the deployment last started from it.
|
|
254
254
|
const target = appRoot(root ?? ".");
|
|
255
|
-
const
|
|
255
|
+
const lock = await readKitLock(target);
|
|
256
|
+
assertLockCompany(lock, tenant);
|
|
257
|
+
const appId = lock?.appId;
|
|
256
258
|
if (!appId)
|
|
257
259
|
throw new CliError("KIT_REQUIRED", `${root ? "That folder" : "The current folder"} is not a deployed Isomorph app: it has no .isomorph/kit.lock.json with an app identity.`, undefined, "Run this from the app's folder, or pass --app-root <path>.");
|
|
258
260
|
const ref = operationRef ?? (await readDeployNote(target, tenant))?.operationRef;
|
|
@@ -7,7 +7,7 @@ import { continueCommand, follow, outcomeFor, pickSourceFailure, recordedRefusal
|
|
|
7
7
|
import { CLI_VERSION } from "./version.js";
|
|
8
8
|
import { assertSafeAppRoot, readAppTree } from "./package.js";
|
|
9
9
|
import { deployBlocked, ensureLinkedApp, kitLockBody } from "./integrations.js";
|
|
10
|
-
import { describeSourceChange, kitPaths, readAppProfile, readDeclaration, readKitLock, sourceDigest } from "./kit.js";
|
|
10
|
+
import { assertLockCompany, describeSourceChange, kitPaths, readAppProfile, readDeclaration, readKitLock, sourceDigest } from "./kit.js";
|
|
11
11
|
import { CHECKS_FAILED_HINT, checksFailedMessage, FLOW_CHECK, readReport, runChecks } from "./check.js";
|
|
12
12
|
import { runCommand } from "./local-runtime.js";
|
|
13
13
|
import { EMBEDDED_KIT_BUNDLE } from "./kit-bundle.js";
|
|
@@ -53,6 +53,7 @@ async function runDeploy(rootArg, governance, output, tenantId, options) {
|
|
|
53
53
|
const lock = await readKitLock(root);
|
|
54
54
|
if (!lock)
|
|
55
55
|
throw new CliError("KIT_REQUIRED", "This folder is not an Isomorph app: it has no .isomorph/kit.lock.json.", undefined, "Run `isomorph init --app-root .` first.");
|
|
56
|
+
assertLockCompany(lock, tenantId);
|
|
56
57
|
// The three values the person confirmed, printed so the transcript shows what
|
|
57
58
|
// was recorded; recorded on the operation before anything is uploaded.
|
|
58
59
|
const profile = await readAppProfile(root);
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { createForwarder } from "./forwarder.js";
|
|
6
6
|
import { readKitLock } from "./kit.js";
|
|
7
7
|
import { GovernanceClient, ensureLinkedApp, fileDeclaredRequests, IDENTITY_WORDS, renderGrantGroup } from "./integrations.js";
|
|
8
|
-
import { acquireDevLock, allocatePorts, installDependencies, LocalRuntime, nodePackageCommand, pidAlive, readDevLock, recordDevChildren, releaseDevLock, runCommand } from "./local-runtime.js";
|
|
8
|
+
import { acquireDevLock, allocatePorts, assertMigrationNames, installDependencies, LocalRuntime, nodePackageCommand, pidAlive, readDevLock, recordDevChildren, releaseDevLock, runCommand } from "./local-runtime.js";
|
|
9
9
|
import { kitPaths } from "./kit.js";
|
|
10
10
|
import { CliError, safeError } from "./output.js";
|
|
11
11
|
/**
|
|
@@ -40,6 +40,7 @@ export async function startDev(root, options) {
|
|
|
40
40
|
const run = options.run ?? runCommand;
|
|
41
41
|
const env = options.env ?? process.env;
|
|
42
42
|
const runtime = new LocalRuntime(root, run);
|
|
43
|
+
await assertMigrationNames(root);
|
|
43
44
|
if (options.reset)
|
|
44
45
|
await runtime.reset(options.output);
|
|
45
46
|
const ports = await allocatePorts();
|
|
@@ -161,6 +162,10 @@ export async function detachDev(root, options) {
|
|
|
161
162
|
const linked = await appId();
|
|
162
163
|
return { origin: running.origin, pid: running.pid, ...(linked ? { appId: linked } : {}) };
|
|
163
164
|
}
|
|
165
|
+
// Refused here, not in the child: the child's refusal would come back as
|
|
166
|
+
// "dev stopped before the app answered … read dev.log", one wrap away from
|
|
167
|
+
// the reason. `--detach` is the shape every agent runs.
|
|
168
|
+
await assertMigrationNames(root);
|
|
164
169
|
await mkdir(kitPaths(root).local, { recursive: true });
|
|
165
170
|
const logFd = openSync(devLogPath(root), "w");
|
|
166
171
|
let exited;
|
|
@@ -21,7 +21,7 @@ Isomorph SSO signs everyone in: no login forms, browser-trusted roles or public
|
|
|
21
21
|
|
|
22
22
|
## Data
|
|
23
23
|
|
|
24
|
-
SQL files
|
|
24
|
+
SQL files \`migrations/NNNN_name.sql\` (four digits, lowercase); applied migrations are frozen after the first deploy (add the next file). Every table: RLS on, one policy; ownership decided in SQL (the gate names any privilege a table still lacks):
|
|
25
25
|
|
|
26
26
|
\`\`\`sql
|
|
27
27
|
ALTER TABLE notes ENABLE ROW LEVEL SECURITY;
|
|
@@ -50,7 +50,7 @@ Shared-read, owner-write: the same, plus \`CREATE POLICY posts_read ON posts FOR
|
|
|
50
50
|
- \`CHECKS_FAILED\`: fix what \`isomorph check\` reports, then rerun.
|
|
51
51
|
- \`DEPLOY_BLOCKED\`, \`INTEGRATIONS_NOT_READY\`, \`AI_NOT_READY\`: IT's step is in the refusal; the app works meanwhile.
|
|
52
52
|
- \`CLI_UPGRADE_REQUIRED\`: \`npm i -g @isomorph.ai/cli\`.
|
|
53
|
-
- \`APP_NOT_FOUND\`:
|
|
53
|
+
- \`APP_NOT_FOUND\`: set \`appId\` and \`tenantId\` to \`""\` in \`.isomorph/kit.lock.json\` to relink.`,
|
|
54
54
|
integrations: `# Company systems (Slack, Gmail, warehouse)
|
|
55
55
|
|
|
56
56
|
## Call shape
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const PUBLISHED_KIT_BUNDLE = {
|
|
2
2
|
"schema": "isomorph.kit-bundle/1.0",
|
|
3
|
-
"kitVersion": "0.7.
|
|
3
|
+
"kitVersion": "0.7.1",
|
|
4
4
|
"sdk": {
|
|
5
5
|
"package": "@isomorph.ai/app-sdk",
|
|
6
6
|
"version": "1.2.2",
|
|
@@ -8,21 +8,21 @@ export const PUBLISHED_KIT_BUNDLE = {
|
|
|
8
8
|
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:8953bb7eb6846bcddf48fd5407c098a13c8ad2cf249d702c35b5666d5af0ad53"
|
|
9
9
|
},
|
|
10
10
|
"images": {
|
|
11
|
-
"appGateway": "public.ecr.aws/y6t4p3i8/harbour-app-gateway@sha256:
|
|
12
|
-
"sessionFixture": "public.ecr.aws/y6t4p3i8/harbour-session-fixture@sha256:
|
|
11
|
+
"appGateway": "public.ecr.aws/y6t4p3i8/harbour-app-gateway@sha256:9d5510db8396cc27c509047f668b63a85ec3892db83cb658c045b2db2d7bddeb",
|
|
12
|
+
"sessionFixture": "public.ecr.aws/y6t4p3i8/harbour-session-fixture@sha256:aec4aa7dfc5128e31448f50088f15550846836ec7ef920368ca2d82b31dbf486"
|
|
13
13
|
},
|
|
14
14
|
"nativeRuntime": {
|
|
15
15
|
"darwinArm64": {
|
|
16
|
-
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:
|
|
17
|
-
"sha256": "
|
|
16
|
+
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:3f4bbf08b814888f7ff64ff968362a944dfba742668927f04a331f8c4850da21",
|
|
17
|
+
"sha256": "3f4bbf08b814888f7ff64ff968362a944dfba742668927f04a331f8c4850da21"
|
|
18
18
|
},
|
|
19
19
|
"linuxX64": {
|
|
20
|
-
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:
|
|
21
|
-
"sha256": "
|
|
20
|
+
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:eea2faf265666cfe46c168d42b60cd0fc5431b43a72b3f409aa63dab7fee8524",
|
|
21
|
+
"sha256": "eea2faf265666cfe46c168d42b60cd0fc5431b43a72b3f409aa63dab7fee8524"
|
|
22
22
|
},
|
|
23
23
|
"windowsX64": {
|
|
24
|
-
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:
|
|
25
|
-
"sha256": "
|
|
24
|
+
"url": "https://public.ecr.aws/v2/y6t4p3i8/harbour-kit-bundle/blobs/sha256:3e1876f0ba701b838a707aa45e6cf4620e06f40aa8c855ce2a65437c752c369a",
|
|
25
|
+
"sha256": "3e1876f0ba701b838a707aa45e6cf4620e06f40aa8c855ce2a65437c752c369a"
|
|
26
26
|
}
|
|
27
27
|
},
|
|
28
28
|
"brief": {
|
|
@@ -46,8 +46,29 @@ export async function readDeclaration(root) {
|
|
|
46
46
|
return { schema: record.schema, connections };
|
|
47
47
|
}
|
|
48
48
|
export function isKitLock(value) {
|
|
49
|
+
return kitLockProblem(value) === undefined;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The first thing wrong with a lock, named, or undefined for a valid one. A
|
|
53
|
+
* builder told to relink by clearing `appId` and `tenantId` wrote `null`, then
|
|
54
|
+
* deleted the keys, and got "does not match isomorph.kit-lock/1.0" twice
|
|
55
|
+
* (bench, 2026-09-16); the refusal now says which field and what it takes.
|
|
56
|
+
*/
|
|
57
|
+
export function kitLockProblem(value) {
|
|
49
58
|
const record = value;
|
|
50
|
-
|
|
59
|
+
if (!record || typeof record !== "object")
|
|
60
|
+
return "it is not a JSON object";
|
|
61
|
+
if (record.schema !== "isomorph.kit-lock/1.0")
|
|
62
|
+
return "`schema` must be \"isomorph.kit-lock/1.0\"";
|
|
63
|
+
for (const field of ["appId", "tenantId"])
|
|
64
|
+
if (typeof record[field] !== "string")
|
|
65
|
+
return `\`${field}\` must be a string (\"\" to relink the app, never null or absent)`;
|
|
66
|
+
if (!isKitBundle(record.bundle))
|
|
67
|
+
return "`bundle` is not a kit bundle manifest";
|
|
68
|
+
for (const field of ["createdAt", "updatedAt"])
|
|
69
|
+
if (typeof record[field] !== "string")
|
|
70
|
+
return `\`${field}\` must be a string`;
|
|
71
|
+
return undefined;
|
|
51
72
|
}
|
|
52
73
|
export async function readKitLock(root) {
|
|
53
74
|
let raw;
|
|
@@ -64,10 +85,22 @@ export async function readKitLock(root) {
|
|
|
64
85
|
catch {
|
|
65
86
|
throw new CliError("KIT_LOCK_INVALID", ".isomorph/kit.lock.json is not valid JSON.");
|
|
66
87
|
}
|
|
67
|
-
|
|
68
|
-
|
|
88
|
+
const problem = kitLockProblem(parsed);
|
|
89
|
+
if (problem !== undefined)
|
|
90
|
+
throw new CliError("KIT_LOCK_INVALID", `.isomorph/kit.lock.json does not match isomorph.kit-lock/1.0: ${problem}.`, undefined, "Fix that field, or delete the file and run `isomorph init --app-root .` to write a fresh one.");
|
|
69
91
|
return parsed;
|
|
70
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* A lock naming another company is refused before any request is made:
|
|
95
|
+
* governance would answer `APP_NOT_FOUND` for an app it never registered, and
|
|
96
|
+
* that reads as a lost app when it is a person connected to the wrong one of
|
|
97
|
+
* their companies (`isomorph connect` lists them as `TENANT_AMBIGUOUS`).
|
|
98
|
+
*/
|
|
99
|
+
export function assertLockCompany(lock, tenantId) {
|
|
100
|
+
if (!lock?.appId || !lock.tenantId || lock.tenantId === tenantId)
|
|
101
|
+
return;
|
|
102
|
+
throw new CliError("APP_NOT_FOUND", `This app is linked to company "${lock.tenantId}" (.isomorph/kit.lock.json), but you are connected to "${tenantId}".`, undefined, `Run \`isomorph connect\` for "${lock.tenantId}" to work on the app there, or set "appId" and "tenantId" to "" in .isomorph/kit.lock.json to link it to "${tenantId}" as a new app.`, undefined, { paths: [".isomorph/kit.lock.json"] });
|
|
103
|
+
}
|
|
71
104
|
export async function writeKitLock(root, lock) {
|
|
72
105
|
const next = { ...lock, updatedAt: new Date().toISOString() };
|
|
73
106
|
await mkdir(dirname(kitPaths(root).lock), { recursive: true });
|
|
@@ -102,6 +102,21 @@ export function nativeGatewayConfig(project, ports, stateDir) {
|
|
|
102
102
|
async function migrationNames(root) {
|
|
103
103
|
return (await readdir(join(root, "migrations")).catch(() => [])).filter(name => name.endsWith(".sql")).sort();
|
|
104
104
|
}
|
|
105
|
+
/** The kit gate's rule for a migration file name (data plane kit_lane.go migrationNameRE), enforced here so `dev` and `check` agree. */
|
|
106
|
+
const MIGRATION_NAME = /^\d{4}_[a-z0-9][a-z0-9_-]{0,95}\.sql$/;
|
|
107
|
+
/**
|
|
108
|
+
* Refuses a migration the gate would refuse, before anything starts. `dev`
|
|
109
|
+
* used to apply `001_init.sql` happily and `check` then refused the same
|
|
110
|
+
* file (bench 2026-09-16, two imported prototypes): the rule is the gate's,
|
|
111
|
+
* stated in its words, at the first command that reads the folder.
|
|
112
|
+
*/
|
|
113
|
+
export async function assertMigrationNames(root) {
|
|
114
|
+
for (const name of await migrationNames(root)) {
|
|
115
|
+
if (MIGRATION_NAME.test(name))
|
|
116
|
+
continue;
|
|
117
|
+
throw new CliError("DEV_FAILED", `migrations/${name} must be a regular file named NNNN_name.sql (four digits, then lowercase letters, digits, _ or -).`, undefined, "Rename the file, then run this again.", undefined, { paths: [`migrations/${name}`] });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
105
120
|
export function internalDatabaseUrl(user = LOCAL.dbUser) { return `postgresql://${user}:${LOCAL.dbPassword}@127.0.0.1/${LOCAL.database}?sslmode=disable`; }
|
|
106
121
|
function uploadKey(project) { return createHash("sha256").update(`upload-key:${project}`).digest("base64"); }
|
|
107
122
|
// ---- Ports and lock ------------------------------------------------------------------
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isomorph.ai/cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Isomorph development kit CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"harbour": {
|
|
38
38
|
"kitBundle": {
|
|
39
39
|
"repository": "public.ecr.aws/y6t4p3i8/harbour-kit-bundle",
|
|
40
|
-
"version": "0.7.
|
|
40
|
+
"version": "0.7.1"
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
}
|