@isomorph.ai/cli 0.3.0 → 0.3.1-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 +4 -2
- package/dist/packages/harbour-cli/src/agent-setup.js +2 -2
- package/dist/packages/harbour-cli/src/cli.js +22 -18
- package/dist/packages/harbour-cli/src/local-runtime.js +1 -1
- package/dist/packages/harbour-cli/src/operations.js +6 -2
- package/dist/packages/harbour-cli/src/productionise.js +6 -4
- package/dist/packages/harbour-cli/src/starter.js +5 -2
- package/dist/packages/harbour-cli/src/version.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -36,12 +36,14 @@ isomorph integrations catalog --app-root <path> [--json] the company's connect
|
|
|
36
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 productionise` 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 productionise --app-root <path> [--
|
|
40
|
-
isomorph status | retry | promote | setup | profile | audience | secrets … --operation <reference>
|
|
39
|
+
isomorph productionise --app-root <path> [--confirm-save] [--json] save + preview deployment, prints the protected link
|
|
40
|
+
isomorph status | retry | promote | setup | profile | audience | secrets … --operation <reference> (promote: [--confirm-tested])
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
`productionise`, `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.
|
|
44
44
|
|
|
45
|
+
`productionise` and `promote` each stop for a typed reply — `Approved.` before the app files are copied to the company code system, `Tested.` before the preview is released. `--confirm-save` and `--confirm-tested` give that same reply on the command line when nobody can type it.
|
|
46
|
+
|
|
45
47
|
`isomorph --help` prints the full usage. Local commands need no company sign-in; integrations and shipping do.
|
|
46
48
|
|
|
47
49
|
## Apps set up by the old `harbour` CLI
|
|
@@ -220,8 +220,8 @@ The kit's rules are the files beside this one: \`core.md\` before the first edit
|
|
|
220
220
|
- "does it work?", and before you report anything as working → open the dev link in your own browser, press the control you built or changed, and read what the app shows. A green \`isomorph check\` is not that proof: fixtures answer AI and company systems, so their refusals appear only when the control is really pressed. Test rendering, navigation, fixtures and approved reads automatically; a Send in development is real: reuse explicit authorization for that bounded test, or ask once if none exists (IT approval alone is not permission to send). If you have no browser, say that the button itself is untested.
|
|
221
221
|
- "I need Slack / Gmail / the warehouse / company data" → read \`integrations.md\` beside this skill first; then, in the same turn, read the catalog, declare only what the app calls, submit the access request yourself, and say READY or PENDING in one line.
|
|
222
222
|
- "summarise", "draft", "explain", "AI" → read \`ai.md\` beside this skill first; then one call behind a control they press.
|
|
223
|
-
- "ship it", "put it online", "let my team try it" → \`isomorph check --app-root . --json\` first, unasked
|
|
224
|
-
- "make it live for everyone", "go to production" → only after they have tried the preview. Before \`isomorph promote --operation <ref> --json\`, show the exact app name, description and production audience again
|
|
223
|
+
- "ship it", "put it online", "let my team try it" → \`isomorph check --app-root . --json\` first, unasked; fix everything it finds: the same gates rerun in the cloud, minutes per attempt. Before sharing or deploying the private preview, show the exact proposed app name, description and audience that will be passed to Isomorph (say "only you" when the audience is empty); ask for one confirmation or correction covering all three; do not run \`productionise\` until they confirm. \`isomorph productionise --app-root . --name "<app name>" --description "<description>" --emails "<comma-separated audience>" --confirm-save --json\` (omit \`--emails\` for "only you"; \`--confirm-save\` is the approval they just gave). It records the confirmed setup before uploading or deploying the app, files the access request for each declared connection itself and refuses with \`INTEGRATIONS_NOT_READY\` naming what IT still has to approve, or \`AI_NOT_READY\` and the one IT step: relay that line only; the app works without them until then. \`result.deployment.protectedUrl\` is the private preview, for them and the confirmed audience after company sign-in. If your tool cuts it off, \`${continueCommand("<ref>")}\` continues the same deployment; never start another one to find out what happened; \`TRANSFORMING\` means building and checking.
|
|
224
|
+
- "make it live for everyone", "go to production" → only after they have tried the preview, which \`--confirm-tested\` states. Before \`isomorph promote --operation <ref> --confirm-tested --json\`, show the exact app name, description and production audience again; ask for one confirmation or correction covering all three; never promote a profile or audience the person has not just seen and confirmed. Report the production link, or that operator approval is pending.
|
|
225
225
|
- "stop it" → \`isomorph stop --app-root .\` (data kept); \`isomorph dev --reset --app-root .\` deletes local data, only when they ask to start over.
|
|
226
226
|
- "every day at 2pm", "run this on a schedule", "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
227
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { RemoteMcpClient } from "./remote-mcp-client.js";
|
|
3
|
-
import { appCallsAi, productionise } from "./productionise.js";
|
|
4
|
-
import { confirmAudience, confirmProfile, continueCommand, dismissSecret, fetchStatus, follow, getAppSetup, listSecrets, promoteToProduction, readSecretFromStdin, readSecretFromTerminal, retryDeployment, setSecret, summarize } from "./operations.js";
|
|
3
|
+
import { appCallsAi, productionise, SAVE_APPROVAL } from "./productionise.js";
|
|
4
|
+
import { confirmAudience, confirmProfile, continueCommand, dismissSecret, fetchStatus, follow, getAppSetup, listSecrets, promoteToProduction, PROMOTION_CONFIRMATION, readSecretFromStdin, readSecretFromTerminal, retryDeployment, setSecret, summarize } from "./operations.js";
|
|
5
5
|
import { CliError, failureEnvelope, operationEnvelope, renderFailure, renderSummary } from "./output.js";
|
|
6
6
|
import { CLI_VERSION } from "./version.js";
|
|
7
7
|
import { connectedAccount, login, logout, refreshStoredToken } from "./auth.js";
|
|
@@ -28,6 +28,9 @@ const profileDescription = optionValue("--description");
|
|
|
28
28
|
const audienceEmails = optionValue("--emails");
|
|
29
29
|
const personal = args.includes("--personal");
|
|
30
30
|
const valueStdin = args.includes("--value-stdin");
|
|
31
|
+
/** The reply the save and promote prompts would otherwise wait to have typed, for a caller with no terminal. */
|
|
32
|
+
const confirmSave = args.includes("--confirm-save");
|
|
33
|
+
const confirmTested = args.includes("--confirm-tested");
|
|
31
34
|
const subcommand = args[1] && !args[1].startsWith("--") ? args[1] : undefined;
|
|
32
35
|
const wait = args.includes("--wait");
|
|
33
36
|
const reset = args.includes("--reset");
|
|
@@ -62,10 +65,10 @@ const usage = [
|
|
|
62
65
|
"Usage:",
|
|
63
66
|
" isomorph connect <work-email | company-start-url>",
|
|
64
67
|
" isomorph login | logout",
|
|
65
|
-
" isomorph productionise --app-root <path> [--include <relative-path>]... [--name <text> --description <text> [--emails a@co,b@co]] [--no-wait] [--max-wait <seconds>] [--json]",
|
|
68
|
+
" isomorph productionise --app-root <path> [--include <relative-path>]... [--name <text> --description <text> [--emails a@co,b@co]] [--confirm-save] [--no-wait] [--max-wait <seconds>] [--json]",
|
|
66
69
|
" isomorph status --operation <reference> [--wait] [--max-wait <seconds>] [--json]",
|
|
67
70
|
" isomorph retry --operation <reference> [--no-wait] [--max-wait <seconds>] [--json]",
|
|
68
|
-
" isomorph promote --operation <reference> [--app-root <path>] [--no-wait] [--max-wait <seconds>] [--json] (--app-root: also confirm the company's AI setup for production when the app calls governed AI)",
|
|
71
|
+
" isomorph promote --operation <reference> [--app-root <path>] [--confirm-tested] [--no-wait] [--max-wait <seconds>] [--json] (--app-root: also confirm the company's AI setup for production when the app calls governed AI)",
|
|
69
72
|
" isomorph setup --operation <reference> [--json] what the app still needs (name, audience, secrets)",
|
|
70
73
|
" isomorph profile --operation <reference> [--name <text>] [--description <text>] [--json]",
|
|
71
74
|
" isomorph audience --operation <reference> [--emails a@co,b@co] [--json] (no --emails = only you)",
|
|
@@ -83,6 +86,7 @@ const usage = [
|
|
|
83
86
|
" isomorph integrations catalog --app-root <path> [--json] the company's connections as .isomorph/integrations.json names them: identifiers, allowed operations, approved channels/tables/views/mailboxes per environment (no app needed)",
|
|
84
87
|
"Run `isomorph connect <work-email>` once; sign in with `isomorph login` when Isomorph asks.",
|
|
85
88
|
"productionise saves the app, follows its deployment, and prints the protected preview link; promote sends a tested preview to production.",
|
|
89
|
+
"productionise stops for a typed `Approved.` before it copies the app files to the company code system, and promote for a typed `Tested.` before it releases the preview. --confirm-save and --confirm-tested give that same reply on the command line when nobody can type it; the approval recorded is the one made by whoever ran the command.",
|
|
86
90
|
`--max-wait bounds how long productionise, status --wait, retry and promote follow the deployment (default 30 minutes). When it passes the command exits 0 with status RUNNING, the last known deployment state, and the \`${continueCommand("<reference>")}\` that continues the same deployment: a bounded wait is not a failure and never means start another one.`,
|
|
87
91
|
"For kit apps, productionise first files the access request for every connection in .isomorph/integrations.json (one per connection; IT approves it once for every environment) and exits 2 (INTEGRATIONS_NOT_READY) naming what IT still has to approve, asks governance whether the company's AI setup is ready when the app calls governed AI (exit 2, AI_NOT_READY, with the IT step), then runs the pipeline's kit gate in the local session and refuses (KIT_GATE_FAILED) what CodeBuild would refuse. promote asks the same AI question for production when --app-root names the app.",
|
|
88
92
|
"secrets set reads the value from your terminal with echo off (or from stdin with --value-stdin); it is never printed or passed to any other program.",
|
|
@@ -95,6 +99,14 @@ const progress = (message) => { process.stderr.write(`${message}\n`); };
|
|
|
95
99
|
const summaryEnvelope = (result) => ({ schema: "isomorph.cli-result/1.0", cliVersion: CLI_VERSION, status: "SUCCEEDED", operationStarted: false, result });
|
|
96
100
|
/** `line` is the human form for a command whose result is a sentence, not a summary. */
|
|
97
101
|
const emit = (value, line) => { process.stdout.write(json ? `${JSON.stringify(value)}\n` : line ?? renderSummary(value)); process.exitCode = 0; };
|
|
102
|
+
/**
|
|
103
|
+
* The command's last bytes, then its exit code. Every non-zero exit goes through
|
|
104
|
+
* here: `process.exitCode` is not ours alone — a dependency's `beforeExit` hook
|
|
105
|
+
* overwrites it (see local-runtime.ts) — and `process.exit` on its own truncates
|
|
106
|
+
* a write still in flight when the stream is a pipe rather than a terminal,
|
|
107
|
+
* which is how an agent or a CI job reads this CLI. Both writes flush first.
|
|
108
|
+
*/
|
|
109
|
+
const exitAfterWriting = (code, out, err = "") => { process.stderr.write(err, () => process.stdout.write(out, () => process.exit(code))); };
|
|
98
110
|
/** Exit 2, like a usage error: nothing started and the fix is a command the maker runs. */
|
|
99
111
|
const USAGE_REFUSALS = ["INTEGRATIONS_NOT_READY", "AI_NOT_READY", "NOT_A_MEMBER", "TENANT_AMBIGUOUS", "NOT_A_START_LINK", "PLATFORM_UNREACHABLE"];
|
|
100
112
|
if (command === "--version" || command === "version") {
|
|
@@ -127,10 +139,7 @@ else if (!["connect", "login", "logout", "productionise", "integrations", ...LOC
|
|
|
127
139
|
|| (command === "integrations" && (!root || !subcommand || !["request", "status", "catalog"].includes(subcommand) || (subcommand === "request" && (!args[2] || args[2].startsWith("--")))))
|
|
128
140
|
|| (OPERATION_COMMANDS.includes(command) && !operationRef)
|
|
129
141
|
|| (command === "secrets" && (!subcommand || !["list", "set", "dismiss"].includes(subcommand) || (subcommand !== "list" && !secretName)))) {
|
|
130
|
-
|
|
131
|
-
process.stderr.write(`${optionError}\n`);
|
|
132
|
-
process.stderr.write(usage);
|
|
133
|
-
process.exit(2);
|
|
142
|
+
exitAfterWriting(2, "", `${optionError ? `${optionError}\n` : ""}${usage}`);
|
|
134
143
|
}
|
|
135
144
|
else {
|
|
136
145
|
try {
|
|
@@ -192,10 +201,8 @@ else {
|
|
|
192
201
|
governance = new GovernanceClient(config.apiUrl, token, config.tenantId);
|
|
193
202
|
}
|
|
194
203
|
const report = await runChecks(target, { run: runCommand, bundle, output: progress, governance });
|
|
195
|
-
if (!report.passed)
|
|
196
|
-
|
|
197
|
-
process.exitCode = 1;
|
|
198
|
-
}
|
|
204
|
+
if (!report.passed)
|
|
205
|
+
exitAfterWriting(1, json ? `${JSON.stringify({ ...summaryEnvelope(report), status: "FAILED", error: { code: "CHECKS_FAILED", message: "One or more checks failed; see .isomorph/local/check-report.json." } })}\n` : "Checks failed; see .isomorph/local/check-report.json.\n");
|
|
199
206
|
else
|
|
200
207
|
emit(summaryEnvelope(report));
|
|
201
208
|
}
|
|
@@ -257,7 +264,7 @@ else {
|
|
|
257
264
|
? { displayName: profileName, description: profileDescription, audienceEmails: audienceEmails ? audienceEmails.split(",").map(value => value.trim()).filter(Boolean) : [] }
|
|
258
265
|
: undefined;
|
|
259
266
|
await assertKitCurrent(appRoot(root));
|
|
260
|
-
const result = await productionise(root, client, progress, tenant, includePaths, { waitForDeployment: !noWait, waitOptions, integrations: { governance: new GovernanceClient(config.apiUrl, token, tenant), bundle: EMBEDDED_KIT_BUNDLE }, ...(confirmedAppSetup ? { confirmedAppSetup } : {}) });
|
|
267
|
+
const result = await productionise(root, client, progress, tenant, includePaths, { waitForDeployment: !noWait, waitOptions, integrations: { governance: new GovernanceClient(config.apiUrl, token, tenant), bundle: EMBEDDED_KIT_BUNDLE }, ...(confirmSave ? { readApproval: async () => SAVE_APPROVAL } : {}), ...(confirmedAppSetup ? { confirmedAppSetup } : {}) });
|
|
261
268
|
envelope = operationEnvelope(result.result, result.operationRef, CLI_VERSION);
|
|
262
269
|
}
|
|
263
270
|
else {
|
|
@@ -273,7 +280,7 @@ else {
|
|
|
273
280
|
if (lock?.appId)
|
|
274
281
|
await assertAiReady(lock.appId, "production", new GovernanceClient(config.apiUrl, token, tenant), progress);
|
|
275
282
|
}
|
|
276
|
-
summary = await promoteToProduction(client, operationRef, progress, { wait: !noWait, waitOptions });
|
|
283
|
+
summary = await promoteToProduction(client, operationRef, progress, { wait: !noWait, waitOptions, ...(confirmTested ? { readConfirmation: async () => PROMOTION_CONFIRMATION } : {}) });
|
|
277
284
|
}
|
|
278
285
|
else if (command === "setup")
|
|
279
286
|
summary = { setup: await getAppSetup(client, operationRef) };
|
|
@@ -295,10 +302,7 @@ else {
|
|
|
295
302
|
}
|
|
296
303
|
catch (error) {
|
|
297
304
|
const envelope = failureEnvelope(error, CLI_VERSION);
|
|
298
|
-
|
|
299
|
-
if (json || command === "productionise")
|
|
300
|
-
process.stdout.write(`${JSON.stringify(envelope)}\n`);
|
|
301
|
-
process.exit(USAGE_REFUSALS.includes(envelope.error?.code ?? "") ? 2 : 1);
|
|
305
|
+
exitAfterWriting(USAGE_REFUSALS.includes(envelope.error?.code ?? "") ? 2 : 1, json || command === "productionise" ? `${JSON.stringify(envelope)}\n` : "", renderFailure(envelope));
|
|
302
306
|
}
|
|
303
307
|
}
|
|
304
308
|
/** One line per (connection, identity): the three lanes are one approval, so they are read together. */
|
|
@@ -4,7 +4,6 @@ import { copyFile, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promi
|
|
|
4
4
|
import { createServer } from "node:net";
|
|
5
5
|
import { dirname, join, relative, win32 } from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
-
import EmbeddedPostgres from "embedded-postgres";
|
|
8
7
|
import { kitPaths, projectName } from "./kit.js";
|
|
9
8
|
import { CliError } from "./output.js";
|
|
10
9
|
export function nodePackageCommand(tool, args, platform = process.platform, execPath = process.execPath) {
|
|
@@ -209,6 +208,7 @@ export class LocalRuntime {
|
|
|
209
208
|
const paths = kitPaths(this.root);
|
|
210
209
|
const postgresBinaries = await hydrateEmbeddedPostgres(this.run);
|
|
211
210
|
const postgresMessages = [];
|
|
211
|
+
const { default: EmbeddedPostgres } = await import("embedded-postgres");
|
|
212
212
|
this.postgres = new EmbeddedPostgres({ databaseDir: join(paths.state, "postgres"), port: this.ports.postgres, user: LOCAL.dbUser, password: LOCAL.dbPassword, persistent: true, onLog: message => postgresMessages.push(String(message).trim()), onError: error => postgresMessages.push(String(error)) });
|
|
213
213
|
try {
|
|
214
214
|
if (!(await readdir(join(paths.state, "postgres")).catch(() => [])).length)
|
|
@@ -250,6 +250,10 @@ export async function retryDeployment(client, operationRef, output, options = {}
|
|
|
250
250
|
return summarize(await fetchStatus(client, operationRef));
|
|
251
251
|
return follow(client, operationRef, output, options.waitOptions);
|
|
252
252
|
}
|
|
253
|
+
/** What the maker types at the promote prompt. `--confirm-tested` supplies this same literal through
|
|
254
|
+
* `readConfirmation`, so governance receives the attestation it receives today, byte for byte — the
|
|
255
|
+
* claim is unchanged, only reachable where no terminal can answer the prompt. */
|
|
256
|
+
export const PROMOTION_CONFIRMATION = "Tested.";
|
|
253
257
|
/**
|
|
254
258
|
* Promotion mirrors the console's "I tested this version" button: the maker
|
|
255
259
|
* must have opened the live preview and say so, and the version they tested
|
|
@@ -268,8 +272,8 @@ export async function promoteToProduction(client, operationRef, output, options
|
|
|
268
272
|
output(`Open the protected preview and test it: ${deployment.protectedUrl}`);
|
|
269
273
|
output("When it works as expected, type Tested. then press Enter to promote it to production.");
|
|
270
274
|
const reply = await (options.readConfirmation ?? readLine)();
|
|
271
|
-
if (reply !==
|
|
272
|
-
throw new CliError("PROMOTION_NOT_CONFIRMED", "The production promotion was not confirmed.", operationRef);
|
|
275
|
+
if (reply !== PROMOTION_CONFIRMATION)
|
|
276
|
+
throw new CliError("PROMOTION_NOT_CONFIRMED", "The production promotion was not confirmed. Confirm the tested preview with --confirm-tested instead.", operationRef);
|
|
273
277
|
const result = structured(await client.call("isomorph_promote_to_production", {
|
|
274
278
|
operationId: operationRef,
|
|
275
279
|
expectedPreviewVersionId: deployment.versionId,
|
|
@@ -12,6 +12,9 @@ import { isProhibitedSecretPath } from "../../../src/secret-paths.js";
|
|
|
12
12
|
import { assertAiReady, assertPreviewIntegrationsReady } from "./integrations.js";
|
|
13
13
|
import { kitPaths, readKitLock, recordKitAppId, sourceDigest } from "./kit.js";
|
|
14
14
|
import { FLOW_CHECK, preflightKitGate, readReport } from "./check.js";
|
|
15
|
+
/** What the maker types at the save prompt; `--confirm-save` supplies this same literal, so the
|
|
16
|
+
* `isomorph.stage-approval/1.0` attestation governance receives is unchanged, byte for byte. */
|
|
17
|
+
export const SAVE_APPROVAL = "Approved.";
|
|
15
18
|
export async function productionise(rootArg, client, output, tenantId, includePaths = [], options = {}) {
|
|
16
19
|
const root = resolve(rootArg);
|
|
17
20
|
output(`Isomorph is checking ${basename(root)}.`);
|
|
@@ -159,9 +162,9 @@ export async function productionise(rootArg, client, output, tenantId, includePa
|
|
|
159
162
|
let execution = prior?.sourceSave?.status && prior.sourceSave.status !== "AWAITING_APPROVAL" ? { status: prior.sourceSave.status } : await execute(client, operationRef, appId, graph);
|
|
160
163
|
if (execution.status === "APPROVAL_REQUIRED" || execution.approvalRequiredBeforeExternalAction === true) {
|
|
161
164
|
output(`Approval required: Isomorph can copy ${graph.deploymentScope.includedFiles.length} app files to the approved company code system. Nothing will be deployed or released. Type Approved. then press Enter.`);
|
|
162
|
-
const approval = await readApproval();
|
|
163
|
-
if (approval !==
|
|
164
|
-
throw new CliError("APPROVAL_NOT_GRANTED", "The Isomorph save was not approved.", operationRef);
|
|
165
|
+
const approval = await (options.readApproval ?? readLine)();
|
|
166
|
+
if (approval !== SAVE_APPROVAL)
|
|
167
|
+
throw new CliError("APPROVAL_NOT_GRANTED", "The Isomorph save was not approved. Approve copying the app files with --confirm-save instead.", operationRef);
|
|
165
168
|
execution = await execute(client, operationRef, appId, graph, { schema: "isomorph.stage-approval/1.0", step: "save_source_baseline", approved: true, approvedByUser: true, userApprovalText: approval, userVisibleProgress: "Approved.", nextStepSummary: "Isomorph can safely save the app.", dataOrActions: ["Save the app in the company code system."] });
|
|
166
169
|
}
|
|
167
170
|
if (execution.status === "ADMIN_SETUP_REQUIRED")
|
|
@@ -437,7 +440,6 @@ function outcomeLine(summary) {
|
|
|
437
440
|
default: return summary.nextStep ?? "Isomorph saved the app; deployment has not started.";
|
|
438
441
|
}
|
|
439
442
|
}
|
|
440
|
-
const readApproval = readLine;
|
|
441
443
|
/**
|
|
442
444
|
* Whether the app really calls governed AI: the gate's inventory of
|
|
443
445
|
* `isomorph.ai.*` call sites (`.isomorph/local/check-report.json`,
|
|
@@ -421,8 +421,11 @@ export function App() {
|
|
|
421
421
|
{error && <p role="alert" style={{ color: "crimson" }}>{error}</p>}
|
|
422
422
|
|
|
423
423
|
{/* Notes — paired with .isomorph/checks/notes-journey.mjs and notes-cross-user.mjs, generated
|
|
424
|
-
from this section and migrations/0001_notes.sql;
|
|
425
|
-
|
|
424
|
+
from this section and migrations/0001_notes.sql; \`isomorph check\` rewrites both whenever
|
|
425
|
+
either one changes. Edit 0001_notes.sql in place only until this app's first deploy: an
|
|
426
|
+
applied migration is frozen, so after that the schema changes by adding the next
|
|
427
|
+
migrations/000N_*.sql — never by editing or deleting an applied file, and never by
|
|
428
|
+
dropping a deployed table. Rules: core.md in the isomorph skill (data). */}
|
|
426
429
|
<section>
|
|
427
430
|
<h2>Notes</h2>
|
|
428
431
|
<form onSubmit={event => { event.preventDefault(); void addNote(); }}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "0.3.
|
|
1
|
+
export const CLI_VERSION = "0.3.1-rc.1";
|