turnzero 0.18.1 → 0.18.3-next.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/turnzero-cloud.mjs +331 -115
- package/package.json +1 -1
package/dist/turnzero-cloud.mjs
CHANGED
|
@@ -200,6 +200,7 @@ var LOCAL_RUN_SETTING = Object.freeze({
|
|
|
200
200
|
});
|
|
201
201
|
var RETIRED_PLATFORM_CREDENTIAL_SETTING = "TURNZERO_CLOUD_TOKEN";
|
|
202
202
|
var LOCAL_RUN_ENVIRONMENT = "local";
|
|
203
|
+
var REMINT_FILE_INSTRUCTION_TAIL = " from it in the development environment file now.";
|
|
203
204
|
var LOCAL_RUN_CREDENTIAL_SETTINGS = Object.freeze([LOCAL_RUN_SETTING.platformCredential, LOCAL_RUN_SETTING.databaseConnection]);
|
|
204
205
|
var ENVIRONMENT_FILE_NAME = ".env";
|
|
205
206
|
var provisionBareLineOf = (call) => commandLineOf(call.origin, PROVISION_SUBCOMMAND, {
|
|
@@ -504,15 +505,21 @@ var planeObjectOf = (text) => {
|
|
|
504
505
|
return null;
|
|
505
506
|
}
|
|
506
507
|
};
|
|
508
|
+
var platformMarked = (text) => {
|
|
509
|
+
const version = planeObjectOf(text)?.contract_version;
|
|
510
|
+
return typeof version === "number" && Number.isInteger(version) && version > 0;
|
|
511
|
+
};
|
|
507
512
|
var redirected = (answer) => answer.type === "opaqueredirect" || answer.status >= 300 && answer.status <= 399;
|
|
508
513
|
var answeredHow = (status) => status >= 100 ? `HTTP ${status}` : "with a redirect";
|
|
509
514
|
var ANSWER_KINDS = Object.freeze(["refused", "failed"]);
|
|
510
515
|
var answerLineOf = (act, kind, status, error) => `${act} ${kind} (HTTP ${status}): ${error}`;
|
|
516
|
+
var PLATFORM_FAILURE_STATUS = 500;
|
|
517
|
+
var notThePlatformLine = (act, status) => `The ${act} was answered HTTP ${status}, not by the platform.`;
|
|
518
|
+
var markedRefusalOf = (status, text) => status < PLATFORM_FAILURE_STATUS && !platformMarked(text) ? null : planeRefusalOf(text);
|
|
511
519
|
var refusalLines = (act, status, refusal) => [
|
|
512
520
|
answerLineOf(act, "refused", status, refusal.error),
|
|
513
521
|
...refusal.detail !== null ? [oneLine(refusal.detail)] : []
|
|
514
522
|
];
|
|
515
|
-
var PLATFORM_FAILURE_STATUS = 500;
|
|
516
523
|
var failureLines = (act, status, said) => said === null ? [`The ${act} was answered HTTP ${status}, a failure of the platform's or of a server between.`] : [answerLineOf(act, "failed", status, said.error), ...said.detail !== null ? [oneLine(said.detail)] : []];
|
|
517
524
|
var HEALTH_READ_PATH = "/healthz";
|
|
518
525
|
var PLANE_CHECK_BOUND_SECONDS = 10;
|
|
@@ -812,7 +819,8 @@ var CREDENTIAL_SOURCES = Object.freeze(["standard_input", "minted_token_variable
|
|
|
812
819
|
var credentialPlaces = (declared) => declared.places ?? `the command reads the grant from its standard input, as the line ${declared.call} answers pipes it in${declared.mintedToken ? `, and otherwise a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, or this computer's token for the platform's address from its credential file, which login and authorize write` : declared.computerToken === true ? `, and otherwise this computer's token for the platform's address from its credential file, which login and authorize write, and never a minted token from ${MINTED_TOKEN_VARIABLE}` : ""}.`;
|
|
813
820
|
var unsetVariableWayOn = (declared) => `To run a line you wrote yourself under this computer's token, unset ${MINTED_TOKEN_VARIABLE} and run it again; or pipe ${declared.aGrant}.`;
|
|
814
821
|
var grantWaysOn = (declared) => `Pipe ${declared.aGrant}, as the line ${declared.call} answers pipes it, or, where you wrote the line yourself, pipe nothing to run it under this computer's token.`;
|
|
815
|
-
var emptyGrantRefusal = (declared) =>
|
|
822
|
+
var emptyGrantRefusal = (declared) => emptyPipeRefusal(declared, grantWaysOn(declared));
|
|
823
|
+
var emptyPipeRefusal = (declared, wayOn) => `The ${declared.grant} piped to the command was empty, so nothing was sent and nothing was started. ${wayOn}`;
|
|
816
824
|
var unendedReadRefusal = (declared, reading, nothingArrived = false) => unendedReadSentence(
|
|
817
825
|
declared.grant,
|
|
818
826
|
reading,
|
|
@@ -828,7 +836,9 @@ async function readCredential(io2, declared, again, line = { named: void 0, clos
|
|
|
828
836
|
const unended = unendedReadingOf(read3);
|
|
829
837
|
if (unended !== null) return { refusal: unendedReadRefusal(declared, unended, read3.bytes === 0) };
|
|
830
838
|
const grant = read3.text.trim();
|
|
831
|
-
if (declared.
|
|
839
|
+
if (declared.emptyPipeRefused === true && grant === "" && read3.bytes > 0) {
|
|
840
|
+
return { refusal: declared.computerToken === true ? emptyGrantRefusal(declared) : emptyPipeRefusal(declared, again) };
|
|
841
|
+
}
|
|
832
842
|
if (grant !== "") {
|
|
833
843
|
if (!UPLOAD_GRANT_FORM.test(grant)) {
|
|
834
844
|
return { refusal: `What the command read as its ${declared.grant} is not ${declared.aGrant}, so nothing was sent and nothing was started. ${again}` };
|
|
@@ -895,7 +905,7 @@ var variableReadSentence = (origin, standing) => {
|
|
|
895
905
|
};
|
|
896
906
|
var fileReadSentence = (origin) => `The credential this run read was this computer's token for ${origin}, from its credential file: where the platform no longer accepts it, run ${LOGIN_SUBCOMMAND}${originWordsOf(origin)} to authorize this computer again.`;
|
|
897
907
|
var noTokenSentence = (origin, expiredAt) => expiredAt === null ? `No credential was read, so nothing was sent: ${MINTED_TOKEN_VARIABLE} is not set, and this computer's credential file holds no token for ${origin}.` : `No credential was read, so nothing was sent: ${MINTED_TOKEN_VARIABLE} is not set, and this computer's token for ${origin} in its credential file expired at ${expiredAt}.`;
|
|
898
|
-
var VARIABLE_ROUTE_SENTENCES = `A tool connected by a minted token, or a job,
|
|
908
|
+
var VARIABLE_ROUTE_SENTENCES = `A tool connected by a minted token, or a job, sets ${MINTED_TOKEN_VARIABLE} from where its token is kept, never by writing the value in a line, and ${ORIGIN_VARIABLE} to the address that minted the token where that is not ${DEFAULT_COMMAND_ORIGIN}, then runs the line again. Such a tool cannot use ${MINT_TOKEN_ACTION}, which takes a sign-in alone. A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy or check line alone and unsets it once that line ends.`;
|
|
899
909
|
var LOGIN_ROUTE_OPENER = "A person logs this computer in once, in a terminal of their own, and its token then stays on this computer for 30 days:";
|
|
900
910
|
var DEPLOY_FILES_ROUTE_SENTENCE = "A folder within the caps of your tool's deploy_files call deploys now through deploy_files, with no login.";
|
|
901
911
|
var loginRouteLines = (io2, origin) => [LOGIN_ROUTE_OPENER, ...loginLinesFor(io2, origin), DEPLOY_FILES_ROUTE_SENTENCE];
|
|
@@ -1107,7 +1117,7 @@ async function revokeUnder(io2, origin, token2, id, userAgent) {
|
|
|
1107
1117
|
if (answered.status >= 200 && answered.status <= 299) {
|
|
1108
1118
|
return revokedRowOf(planeObjectOf(answered.text), id) ? { revoked: true } : { lines: [`${REVOKE_TOKEN_ACTION} was answered HTTP ${answered.status} with no revocation of token ${id} the command can read.`], known: false };
|
|
1109
1119
|
}
|
|
1110
|
-
const refusal =
|
|
1120
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
1111
1121
|
if (refusal === null) return { lines: [`${REVOKE_TOKEN_ACTION} was answered HTTP ${answered.status}, not by the platform.`], known: false };
|
|
1112
1122
|
if (NO_LONGER_SERVES.includes(answered.status)) return { ended: true };
|
|
1113
1123
|
return { lines: refusalLines(REVOKE_TOKEN_ACTION, answered.status, refusal), known: true };
|
|
@@ -1155,8 +1165,8 @@ async function readAccountUnder(io2, origin, token2, userAgent) {
|
|
|
1155
1165
|
const account = accountOf(planeObjectOf(answered.text));
|
|
1156
1166
|
return account !== null ? { account } : { lines: [`The ${ACCOUNT_READ_ACT} was answered HTTP ${answered.status} with no account the command can read.`], refused: false };
|
|
1157
1167
|
}
|
|
1158
|
-
const refusal =
|
|
1159
|
-
if (refusal === null) return { lines: [
|
|
1168
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
1169
|
+
if (refusal === null) return { lines: [notThePlatformLine(ACCOUNT_READ_ACT, answered.status)], refused: false };
|
|
1160
1170
|
return { lines: refusalLines(ACCOUNT_READ_ACT, answered.status, refusal), refused: true };
|
|
1161
1171
|
}
|
|
1162
1172
|
var whoamiLineFor = (io2, origin) => printedLineOf(io2, commandLineOf(origin, WHOAMI_SUBCOMMAND, origin !== DEFAULT_COMMAND_ORIGIN ? { origin } : {}), origin);
|
|
@@ -1225,9 +1235,9 @@ async function completeAct(io2, origin, kind, code, userAgent, say, deliver = ()
|
|
|
1225
1235
|
const answer = planeObjectOf(answered.text);
|
|
1226
1236
|
if (answer !== null && typeof answer.value === "string") io2.hide(answer.value, REDACTED_TOKEN);
|
|
1227
1237
|
if (answered.status < 200 || answered.status > 299) {
|
|
1228
|
-
const refusal =
|
|
1238
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
1229
1239
|
if (refusal === null) {
|
|
1230
|
-
say(
|
|
1240
|
+
say(notThePlatformLine(EXCHANGE_ACT, answered.status));
|
|
1231
1241
|
say(unknown);
|
|
1232
1242
|
return COMMAND_STATUS.unknown;
|
|
1233
1243
|
}
|
|
@@ -1573,6 +1583,79 @@ function strFromU8(dat, latin1) {
|
|
|
1573
1583
|
}
|
|
1574
1584
|
}
|
|
1575
1585
|
|
|
1586
|
+
// src/session_free_paths.ts
|
|
1587
|
+
var SESSION_FREE_SIGN_IN_PREFIX = "/__account/";
|
|
1588
|
+
var SESSION_FREE_ROUTER_PREFIX = "/__router/";
|
|
1589
|
+
|
|
1590
|
+
// src/client_files.ts
|
|
1591
|
+
var MiB = 1024 * 1024;
|
|
1592
|
+
var CLIENT_FOLDER = "client/";
|
|
1593
|
+
var CLIENT_FILES_MAX = 2e3;
|
|
1594
|
+
var CLIENT_FILE_MAX_BYTES = 25 * MiB;
|
|
1595
|
+
var CLIENT_FILES_TOTAL_MAX_BYTES = 50 * MiB;
|
|
1596
|
+
var CLIENT_NAME_MAX_CHARS = 512;
|
|
1597
|
+
var CLIENT_NAME_MAX_SEGMENTS = 32;
|
|
1598
|
+
var PRUNE_FILE_DELETE_BUDGET_MS = 20 * 6e4;
|
|
1599
|
+
var INDEX_CACHE_BYTES = 32 * MiB;
|
|
1600
|
+
var CONTENT_CACHE_BYTES = 128 * MiB;
|
|
1601
|
+
var CONTENT_CACHE_FILE_MAX_BYTES = 1 * MiB;
|
|
1602
|
+
var SHELL_FILES = Object.freeze([
|
|
1603
|
+
{ name: "index.html", paths: ["/", "/index.html"] },
|
|
1604
|
+
{ name: "app.js", paths: ["/app.js"] },
|
|
1605
|
+
{ name: "styles.css", paths: ["/styles.css"] }
|
|
1606
|
+
]);
|
|
1607
|
+
var SHELL_NAMES = new Set(SHELL_FILES.map((file) => file.name));
|
|
1608
|
+
var SHELL_PATHS = new Set(SHELL_FILES.flatMap((file) => file.paths));
|
|
1609
|
+
var CLIENT_CONTENT_TYPES = Object.freeze({
|
|
1610
|
+
".html": "text/html",
|
|
1611
|
+
".css": "text/css",
|
|
1612
|
+
".js": "text/javascript",
|
|
1613
|
+
".mjs": "text/javascript",
|
|
1614
|
+
".json": "application/json",
|
|
1615
|
+
".map": "application/json",
|
|
1616
|
+
".svg": "image/svg+xml",
|
|
1617
|
+
".png": "image/png",
|
|
1618
|
+
".jpg": "image/jpeg",
|
|
1619
|
+
".jpeg": "image/jpeg",
|
|
1620
|
+
".gif": "image/gif",
|
|
1621
|
+
".webp": "image/webp",
|
|
1622
|
+
".avif": "image/avif",
|
|
1623
|
+
".ico": "image/x-icon",
|
|
1624
|
+
".woff": "font/woff",
|
|
1625
|
+
".woff2": "font/woff2",
|
|
1626
|
+
".ttf": "font/ttf",
|
|
1627
|
+
".otf": "font/otf",
|
|
1628
|
+
".wasm": "application/wasm",
|
|
1629
|
+
".txt": "text/plain",
|
|
1630
|
+
".xml": "application/xml",
|
|
1631
|
+
".webmanifest": "application/manifest+json",
|
|
1632
|
+
".pdf": "application/pdf",
|
|
1633
|
+
".mp3": "audio/mpeg",
|
|
1634
|
+
".ogg": "audio/ogg",
|
|
1635
|
+
".oga": "audio/ogg",
|
|
1636
|
+
".wav": "audio/wav",
|
|
1637
|
+
".m4a": "audio/mp4",
|
|
1638
|
+
".flac": "audio/flac",
|
|
1639
|
+
".mp4": "video/mp4",
|
|
1640
|
+
".webm": "video/webm",
|
|
1641
|
+
".ogv": "video/ogg"
|
|
1642
|
+
});
|
|
1643
|
+
var RESERVED_TOP_FOLDERS = Object.freeze([SESSION_FREE_SIGN_IN_PREFIX, SESSION_FREE_ROUTER_PREFIX].map((prefix) => prefix.replace(/^\/|\/$/g, "").toLowerCase()));
|
|
1644
|
+
var ADMITTED_SEGMENT = /^[A-Za-z0-9\-_.~]+$/;
|
|
1645
|
+
function admittedClientName(path) {
|
|
1646
|
+
if (!isClientPath(path)) return null;
|
|
1647
|
+
const name = path.slice(CLIENT_FOLDER.length);
|
|
1648
|
+
if (name.length > CLIENT_NAME_MAX_CHARS) return null;
|
|
1649
|
+
const segments = name.split("/");
|
|
1650
|
+
if (segments.length > CLIENT_NAME_MAX_SEGMENTS) return null;
|
|
1651
|
+
for (const segment of segments) {
|
|
1652
|
+
if (!ADMITTED_SEGMENT.test(segment) || segment.startsWith(".") || segment.endsWith(".")) return null;
|
|
1653
|
+
}
|
|
1654
|
+
if (RESERVED_TOP_FOLDERS.includes(segments[0].toLowerCase())) return null;
|
|
1655
|
+
return name;
|
|
1656
|
+
}
|
|
1657
|
+
var isClientPath = (path) => path.startsWith(CLIENT_FOLDER) && path.length > CLIENT_FOLDER.length;
|
|
1658
|
+
|
|
1576
1659
|
// src/turnzero_cloud_command/export_download.ts
|
|
1577
1660
|
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2, relative as relative2, resolve as resolve2 } from "node:path";
|
|
1578
1661
|
|
|
@@ -2218,7 +2301,7 @@ var readHealthPath = (path, entries) => {
|
|
|
2218
2301
|
return answer("absent");
|
|
2219
2302
|
};
|
|
2220
2303
|
var shownHealthPath = (path) => shownNoteName(bareHealthPath(path));
|
|
2221
|
-
var healthLineOf = (path) => `${WARNING_LINE_HEAD}${MANIFEST_FILE} names the health path ${shownHealthPath(path)}, and no other file of the zip holds its last segment, package files and documentation set aside. A deploy waits for that path to answer 200, so check that the application serves it. A route built in another form is not seen here.`;
|
|
2304
|
+
var healthLineOf = (path) => `${WARNING_LINE_HEAD}${MANIFEST_FILE} names the health path ${shownHealthPath(path)}, and no other file of the zip holds its last segment, package files and documentation set aside. A deploy waits for that path to answer 200 three times in a row, so check that the application serves it. A route built in another form is not seen here.`;
|
|
2222
2305
|
var CREDENTIAL_FILE_NAMES = Object.freeze(["id_rsa", "id_dsa", "id_ecdsa", "id_ed25519", ".netrc", ".pgpass", ".htpasswd", "credentials.json"]);
|
|
2223
2306
|
var CLIENT_SECRET_OPENING = "client_secret";
|
|
2224
2307
|
var CLIENT_SECRET_ENDING = ".json";
|
|
@@ -2292,11 +2375,14 @@ var DeployRefusal = class extends Error {
|
|
|
2292
2375
|
* row's outcome, a failed gate's `gate`, its evidence (PLD-L0-59;
|
|
2293
2376
|
* PLD-L0-63); onto the wire, an `upload_hash_mismatch`'s
|
|
2294
2377
|
* `received_sha256` and `expected_sha256` (deploy_upload.ts's readUpload;
|
|
2295
|
-
* PLD-L0-86), which the upload form's read throws before any row
|
|
2296
|
-
*
|
|
2297
|
-
*
|
|
2298
|
-
*
|
|
2299
|
-
*
|
|
2378
|
+
* PLD-L0-86), which the upload form's read throws before any row, and a
|
|
2379
|
+
* `deploy_files_too_large`'s `limit`, the cap it met (deploy_files.ts's
|
|
2380
|
+
* tooLarge; #4418; MAPI-26); empty for every other refusal. Never
|
|
2381
|
+
* `error` or `detail` themselves — failRun keeps those two first. The
|
|
2382
|
+
* wire mapping (main.ts's actRefusal) reads status, code, and detail, the
|
|
2383
|
+
* two hashes for `upload_hash_mismatch` alone, and `limit` for
|
|
2384
|
+
* `deploy_files_too_large` alone; a refusal thrown after the 202 reaches
|
|
2385
|
+
* the row, not the wire. */
|
|
2300
2386
|
members;
|
|
2301
2387
|
/** The fields the control plane's `version failed` line carries beside
|
|
2302
2388
|
* the refusal for the operator alone (MAPI-10), never the row's or the
|
|
@@ -2584,7 +2670,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
|
|
|
2584
2670
|
var ROOT_FILE_MAX_BYTES = 1024 * 1024;
|
|
2585
2671
|
var MEMBER_FILE_MAX_BYTES = 64 * 1024;
|
|
2586
2672
|
var MEMBER_FILES_READ_MAX = 50;
|
|
2587
|
-
var WINDOWS_ZIP_COMMAND = "tar.exe -a -c -f app.zip --exclude node_modules --exclude .env --exclude '.env.*' --exclude .git -C app .";
|
|
2673
|
+
var WINDOWS_ZIP_COMMAND = "tar.exe -a -c -f app.zip --exclude node_modules --exclude .env --exclude '.env.*' --exclude .git --exclude .claude --exclude .mcp.json --exclude .codex --exclude .cursor --exclude .gemini --exclude .continue --exclude .roo --exclude .kiro --exclude .amazonq --exclude .aider.conf.yml -C app .";
|
|
2588
2674
|
function readArtifact(bytes) {
|
|
2589
2675
|
const stored = [];
|
|
2590
2676
|
const names = [];
|
|
@@ -2766,11 +2852,48 @@ function manifestCopyDepthRefusal(reading) {
|
|
|
2766
2852
|
throw new DeployRefusal(400, "artifact_layout_invalid", `The zip's manifest.json nests deeper than the ${MANIFEST_COPY_DEPTH_MAX} levels the deploy reads: the platform reads the manifest submit_manifest recorded, so flatten the zip's copy or leave it out, and deploy again (PLD-L0-63).`);
|
|
2767
2853
|
}
|
|
2768
2854
|
}
|
|
2855
|
+
function clientReadingOf(records) {
|
|
2856
|
+
const admitted = /* @__PURE__ */ new Map();
|
|
2857
|
+
const skipped = [];
|
|
2858
|
+
const skippedSeen = /* @__PURE__ */ new Set();
|
|
2859
|
+
for (const record of records) {
|
|
2860
|
+
const { path, folder } = entryNameOf(record.name);
|
|
2861
|
+
if (folder || !isClientPath(path)) continue;
|
|
2862
|
+
const name = admittedClientName(path);
|
|
2863
|
+
if (name !== null) admitted.set(name, record);
|
|
2864
|
+
else if (!skippedSeen.has(path)) {
|
|
2865
|
+
skippedSeen.add(path);
|
|
2866
|
+
skipped.push(path);
|
|
2867
|
+
}
|
|
2868
|
+
}
|
|
2869
|
+
return { admitted, skipped };
|
|
2870
|
+
}
|
|
2871
|
+
function clientBoundRefusal(reading) {
|
|
2872
|
+
const cite = " (SVC-L0-26).";
|
|
2873
|
+
if (reading.admitted.size > CLIENT_FILES_MAX) {
|
|
2874
|
+
return `The zip's client/ folder holds ${reading.admitted.size} files the publish admits, more than the ${CLIENT_FILES_MAX} files one version publishes: ship fewer client files, and deploy again${cite}`;
|
|
2875
|
+
}
|
|
2876
|
+
let total = 0;
|
|
2877
|
+
for (const [name, record] of reading.admitted) {
|
|
2878
|
+
const declared = declaredBytesOf(record);
|
|
2879
|
+
if (declared > CLIENT_FILE_MAX_BYTES) {
|
|
2880
|
+
return `The zip's client file ${shownEntry(`${CLIENT_FOLDER}${name}`)} declares ${declared} bytes, more than the 25 MiB (${CLIENT_FILE_MAX_BYTES} bytes) one client file holds: keep long media in a storage area, and deploy again${cite}`;
|
|
2881
|
+
}
|
|
2882
|
+
total += declared;
|
|
2883
|
+
}
|
|
2884
|
+
if (total > CLIENT_FILES_TOTAL_MAX_BYTES) {
|
|
2885
|
+
return `The zip's client files declare ${total} bytes in all, more than the 50 MiB (${CLIENT_FILES_TOTAL_MAX_BYTES} bytes) one version's client files hold: keep large media in a storage area, and deploy again${cite}`;
|
|
2886
|
+
}
|
|
2887
|
+
return null;
|
|
2888
|
+
}
|
|
2769
2889
|
function checkArtifact(bytes) {
|
|
2770
2890
|
const reading = readArtifact(bytes);
|
|
2771
2891
|
const packageJson = artifactLayoutOf(reading);
|
|
2772
2892
|
manifestCopyDepthRefusal(reading);
|
|
2773
|
-
|
|
2893
|
+
const client = clientReadingOf(reading.records);
|
|
2894
|
+
const bounded = clientBoundRefusal(client);
|
|
2895
|
+
if (bounded !== null) throw new DeployRefusal(400, "artifact_layout_invalid", bounded);
|
|
2896
|
+
return { reading, packageJson, client };
|
|
2774
2897
|
}
|
|
2775
2898
|
function healthPathReadingOf(bytes, reading, recorded) {
|
|
2776
2899
|
const path = isRecord(recorded) && typeof recorded.health === "string" ? recorded.health : null;
|
|
@@ -3292,7 +3415,7 @@ async function prepareDeploy(io2, options, zipSha256Value, bearer, userAgent, re
|
|
|
3292
3415
|
return nothingStarted;
|
|
3293
3416
|
}
|
|
3294
3417
|
if (answered.status < 200 || answered.status > 299) {
|
|
3295
|
-
const refusal =
|
|
3418
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
3296
3419
|
if (refusal === null) {
|
|
3297
3420
|
io2.print(prepareNotThePlatform(answered.status));
|
|
3298
3421
|
io2.print(PREPARE_AGAIN);
|
|
@@ -3496,7 +3619,7 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
|
|
|
3496
3619
|
return PROGRESS_EXIT_STATUS.refused;
|
|
3497
3620
|
}
|
|
3498
3621
|
const answer = planeObjectOf(started.text);
|
|
3499
|
-
if (answer === null) {
|
|
3622
|
+
if (answer === null || (started.status < 200 || started.status > 299) && !platformMarked(started.text)) {
|
|
3500
3623
|
io2.print(`The start was answered HTTP ${started.status}, not by the platform, so the deploy may have started.`);
|
|
3501
3624
|
io2.print(stop);
|
|
3502
3625
|
return PROGRESS_EXIT_STATUS.refused;
|
|
@@ -3543,8 +3666,8 @@ async function deploy(named, io2, flags = /* @__PURE__ */ new Set()) {
|
|
|
3543
3666
|
return PROGRESS_EXIT_STATUS.refused;
|
|
3544
3667
|
}
|
|
3545
3668
|
if (read2.status !== 200 || !read2.type.startsWith("text/plain")) {
|
|
3546
|
-
const refusal =
|
|
3547
|
-
const said = refusal !== null ? refusalLines(PROGRESS_READ_ACT, read2.status, refusal) : [
|
|
3669
|
+
const refusal = markedRefusalOf(read2.status, read2.text);
|
|
3670
|
+
const said = refusal !== null ? refusalLines(PROGRESS_READ_ACT, read2.status, refusal) : [notThePlatformLine(PROGRESS_READ_ACT, read2.status)];
|
|
3548
3671
|
for (const line of said) io2.print(line);
|
|
3549
3672
|
io2.print(stop);
|
|
3550
3673
|
return PROGRESS_EXIT_STATUS.refused;
|
|
@@ -3584,9 +3707,9 @@ var DEPLOY_ROW = Object.freeze({
|
|
|
3584
3707
|
variables: [[DEPLOY_NO_WAIT_VARIABLE, "any value returns the command once the deploy has started, without waiting for it."]],
|
|
3585
3708
|
note: [
|
|
3586
3709
|
`A deploy is one line, which carries no credential: run the line your tool's deploy call answers, or ${DEPLOY_SUBCOMMAND} ${DEPLOY_OPTION.application} <application id>, from the application's folder. It reads no standard input. On an authorized computer it runs under this computer's token, which ${LOGIN_SUBCOMMAND} or ${AUTHORIZE_SUBCOMMAND} keeps in the credential file for each platform address, and sends it only to the address it is kept under: ${DEPLOY_OPTION.origin} where the line names one, else the address ${ORIGIN_VARIABLE} holds, else ${DEFAULT_COMMAND_ORIGIN}.`,
|
|
3587
|
-
`On a computer with no token for that address, or one that has expired, it sends nothing, ends 3, and prints two routes, in this order. A tool connected by a minted token, or a job with no tool, such as a CI job's,
|
|
3710
|
+
`On a computer with no token for that address, or one that has expired, it sends nothing, ends 3, and prints two routes, in this order. A tool connected by a minted token, or a job with no tool, such as a CI job's, sets ${MINTED_TOKEN_VARIABLE} from where its token is kept, such as the job's secret store, never by writing the value in a line. It sets ${ORIGIN_VARIABLE} to the address that minted the token where that is not ${DEFAULT_COMMAND_ORIGIN}, then runs the deploy line again. Such a tool cannot use ${MINT_TOKEN_ACTION}, which takes a sign-in alone.`,
|
|
3588
3711
|
`Otherwise a person logs this computer in once with ${LOGIN_SUBCOMMAND}, in a terminal of their own, then runs the deploy line again; a folder within the caps of the tool's deploy_files call deploys now through deploy_files, with no login. The credential file is ${CREDENTIAL_FILE_WORDS}.`,
|
|
3589
|
-
`A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy or check line alone and unsets it once that line ends, since the command cannot unset a variable of the shell that started it. A job's token is minted for the application, with an expiry. The variable's token wins over the credential file: the command makes the preparing call under that token, and sends the token in no other request.`,
|
|
3712
|
+
`A tool sets ${MINTED_TOKEN_VARIABLE} for the deploy or check line alone and unsets it once that line ends, since the command cannot unset a variable of the shell that started it. A value a job's environment already supplies stays as it stands. A job's token is minted for the application, with an expiry. The variable's token wins over the credential file: the command makes the preparing call under that token, and sends the token in no other request.`,
|
|
3590
3713
|
`Under the variable, the command sends its token only to the address ${ORIGIN_VARIABLE} holds, or to ${DEFAULT_COMMAND_ORIGIN} where that variable is unset: under the token, ${DEPLOY_OPTION.origin} may name that address alone, and where the line names none the command addresses it. ${DEPLOY_OPTION.env} names the environment where a line names one, ${COMMAND_ENVIRONMENTS.join(" or ")}; where none is named, the platform chooses, and the command deploys to the environment the platform answers.`,
|
|
3591
3714
|
`A progress read answered 502, 503, or 504, which is often temporary, is made again, at most three more times, and an upload answered 503 credential_lookup_busy, which wrote nothing, is made again after the wait it names, at most twice.`,
|
|
3592
3715
|
`After its zip line, ${DEPLOY_SUBCOMMAND} prints the folder's top-level entries the zip holds on one line, at most ten and then the count of the rest, and ${HASH_SUBCOMMAND} ${HASH_OPTION.list} names every file; no such line prints for a .zip file.`,
|
|
@@ -3713,7 +3836,7 @@ async function callCheck(io2, options, bearer, userAgent, refused = () => null)
|
|
|
3713
3836
|
return nothingChecked;
|
|
3714
3837
|
}
|
|
3715
3838
|
if (answered.status < 200 || answered.status > 299) {
|
|
3716
|
-
const refusal =
|
|
3839
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
3717
3840
|
if (refusal === null) {
|
|
3718
3841
|
io2.print(checkNotThePlatform(answered.status));
|
|
3719
3842
|
io2.print(CHECK_AGAIN);
|
|
@@ -3828,7 +3951,7 @@ var CHECK_ROW = Object.freeze({
|
|
|
3828
3951
|
note: [
|
|
3829
3952
|
`A check is one line, which carries no credential: run the line your tool's ${CHECK_ACTION} call answers, or ${CHECK_SUBCOMMAND} ${CHECK_OPTION.application} <application id>, from the application's folder, before a first deploy and after each fix. It reads its credential and the platform's address as ${DEPLOY_SUBCOMMAND} does, builds the zip ${DEPLOY_SUBCOMMAND} builds, and sends the platform the application alone, no byte of the zip and no hash. It uploads nothing and starts nothing.`,
|
|
3830
3953
|
`After its zip line, ${CHECK_SUBCOMMAND} prints the further lines ${DEPLOY_SUBCOMMAND} prints, leaving out the health warning where the platform says the deploy would be a first one, since a refused: line then reads the recorded health path. Then it prints one refused: line for each refusal the platform answered for the application's state, then one for each refusal of the zip by the deploy's own checks, a line naming submit_manifest where the zip's manifest.json names another health path, and a last checked: line counting the refusals.`,
|
|
3831
|
-
`A check
|
|
3954
|
+
`A passing check does not guarantee the deploy is accepted: a deploy in flight, the day's deploys, and the hosting cell's room can change before the deploy, which makes every check again.`
|
|
3832
3955
|
],
|
|
3833
3956
|
afterOwnError: CHECK_AFTER_OWN_ERROR,
|
|
3834
3957
|
run: (call, io2) => check(call.named, io2)
|
|
@@ -4026,7 +4149,8 @@ var SECRET_CREDENTIAL = Object.freeze({
|
|
|
4026
4149
|
aGrant: "a secret grant",
|
|
4027
4150
|
call: "the store_secret or rotate_secret call",
|
|
4028
4151
|
mintedToken: false,
|
|
4029
|
-
computerToken: true
|
|
4152
|
+
computerToken: true,
|
|
4153
|
+
emptyPipeRefused: true
|
|
4030
4154
|
});
|
|
4031
4155
|
var freshLineSteps = (act) => `${SECRET_ACTION[act]} from your tool with the name and its scope, naming no value, and run the line it answers as given.`;
|
|
4032
4156
|
var secretAgain = (act) => `Call ${freshLineSteps(act)}`;
|
|
@@ -4176,7 +4300,7 @@ async function secret(call, io2) {
|
|
|
4176
4300
|
}
|
|
4177
4301
|
const answer = planeObjectOf(answered.text);
|
|
4178
4302
|
if (answered.status < 200 || answered.status > 299) {
|
|
4179
|
-
const refusal =
|
|
4303
|
+
const refusal = markedRefusalOf(answered.status, answered.text);
|
|
4180
4304
|
if (refusal === null) {
|
|
4181
4305
|
io2.print(`${action} was answered HTTP ${answered.status}, not by the platform.`);
|
|
4182
4306
|
io2.print(unknown);
|
|
@@ -4394,10 +4518,12 @@ var RUN_OPTIONS = Object.freeze([
|
|
|
4394
4518
|
}
|
|
4395
4519
|
]);
|
|
4396
4520
|
var NO_APPLICATION_STARTED = "No application was started.";
|
|
4521
|
+
var RUN_STEP_BACKGROUND = "The line runs until the application ends, so start it as a background process and read its lines";
|
|
4397
4522
|
var PORT_FORM = /^[1-9][0-9]{0,4}$/;
|
|
4398
4523
|
var isPortNumber = (value) => PORT_FORM.test(value) && Number(value) <= 65535;
|
|
4399
4524
|
var RUN_BACKGROUND_SENTENCE = "The command runs until the application ends, so a tool starts it as a background process and reads its lines.";
|
|
4400
|
-
var
|
|
4525
|
+
var stopSentenceOf = (processId, onWindows2 = NPM_NEEDS_SHELL) => `To stop it, send it an interrupt or run ${onWindows2 ? `taskkill /T /F /PID ${processId}` : `kill ${processId}`}, which ends the application with the command.`;
|
|
4526
|
+
var startingLineOf = (folder, processId, onWindows2 = NPM_NEEDS_SHELL) => `Starting npm start in ${folder}; the command's process id is ${processId}. ${RUN_BACKGROUND_SENTENCE} ${stopSentenceOf(processId, onWindows2)}`;
|
|
4401
4527
|
var PORT_SOURCE_WORDS = Object.freeze({
|
|
4402
4528
|
option: RUN_OPTION.port,
|
|
4403
4529
|
file: "the environment file",
|
|
@@ -4511,7 +4637,8 @@ var npmEntryOf = (io2, nodePath) => npmEntryCandidatesOf(io2.env, nodePath).find
|
|
|
4511
4637
|
return false;
|
|
4512
4638
|
}
|
|
4513
4639
|
}) ?? null;
|
|
4514
|
-
var npmStartOf = (viaShell = NPM_NEEDS_SHELL, entry = null, nodePath = process.execPath) => entry !== null ? { command: nodePath, args: [entry,
|
|
4640
|
+
var npmStartOf = (viaShell = NPM_NEEDS_SHELL, entry = null, nodePath = process.execPath) => entry !== null ? { command: nodePath, args: [entry, ...START_ARGS] } : viaShell ? { command: ["npm", ...START_ARGS].join(" "), args: [], shell: true } : { command: "npm", args: START_ARGS };
|
|
4641
|
+
var START_ARGS = Object.freeze(["start", "--no-update-notifier"]);
|
|
4515
4642
|
var npmRunOf = (words, viaShell = NPM_NEEDS_SHELL, entry = null, nodePath = process.execPath) => entry !== null ? { command: nodePath, args: [entry, ...words] } : viaShell ? { command: ["npm", ...words].join(" "), args: [], shell: true } : { command: "npm", args: words };
|
|
4516
4643
|
var INTERRUPTED_ON_WINDOWS = 'The application has ended. cmd may ask "Terminate batch job (Y/N)?" next: either answer returns the prompt.';
|
|
4517
4644
|
var afterInterruptLineOf = (ended, onWindows2 = NPM_NEEDS_SHELL) => onWindows2 && ended.interrupted === true ? INTERRUPTED_ON_WINDOWS : null;
|
|
@@ -4556,7 +4683,7 @@ async function localRun(call, io2) {
|
|
|
4556
4683
|
}
|
|
4557
4684
|
const { env, overridden } = applicationEnvironmentOf(io2.env, read2.settings, options.port);
|
|
4558
4685
|
for (const [text, shown2] of hiddenTextsOf(Object.entries(env))) io2.hide(text, shown2);
|
|
4559
|
-
io2.print(startingLineOf(options.folder));
|
|
4686
|
+
io2.print(startingLineOf(options.folder, io2.processId ?? process.pid, io2.windows ?? NPM_NEEDS_SHELL));
|
|
4560
4687
|
if (file.text === null) io2.print(`No environment file stands at ${options.envFile}, so the application receives the shell's environment alone.`);
|
|
4561
4688
|
else if (read2.settings.length === 0) io2.print(`${options.envFile} sets no setting.`);
|
|
4562
4689
|
else io2.print(`${options.envFile} sets ${read2.settings.length} setting(s): ${read2.settings.map(([name]) => name).join(", ")}.`);
|
|
@@ -4795,7 +4922,7 @@ var readOf = async (io2, origin, userAgent, body, what) => {
|
|
|
4795
4922
|
}
|
|
4796
4923
|
const answer = planeObjectOf(text);
|
|
4797
4924
|
if (status >= 200 && status <= 299 && answer !== null) return { answer };
|
|
4798
|
-
const refusal =
|
|
4925
|
+
const refusal = markedRefusalOf(status, text);
|
|
4799
4926
|
if (refusal === null) return { lines: [`${LIBRARY_READ_ACTION} was answered HTTP ${status}, not by the platform. ${NOTHING_WRITTEN2}`] };
|
|
4800
4927
|
return { lines: [...refusalLines(LIBRARY_READ_ACTION, status, refusal), NOTHING_WRITTEN2] };
|
|
4801
4928
|
};
|
|
@@ -5050,6 +5177,7 @@ var committedFilesOf = (copied, written) => {
|
|
|
5050
5177
|
return `${files.slice(0, -1).join(", ")}${files.length > 2 ? "," : ""} and ${files[files.length - 1]}`;
|
|
5051
5178
|
};
|
|
5052
5179
|
var COPY_PACKAGES_CLAUSE = "copy the packages line into the application manifest's packages member at each submission; ";
|
|
5180
|
+
var KEEP_PACKAGES_CLAUSE = "the application manifest was written whole, so read it again before patching it, and carry its packages member over in any later rewrite; ";
|
|
5053
5181
|
var installFailedSentence = (folder) => `npm install in ${folder} did not finish: the entry, its row, and the copy are written. Run npm install there again; running this line again takes the entry again whole.`;
|
|
5054
5182
|
var LIBRARY_AFTER_OWN_ERROR = "the entry's folder, its manifest row, or the application's copy may stand in part, and running the line again replaces each whole";
|
|
5055
5183
|
async function readTake(io2, options, userAgent, planOf, planeChecked = false) {
|
|
@@ -5183,7 +5311,7 @@ async function installTake(io2, root, plan, manifestText, application) {
|
|
|
5183
5311
|
io2.print(`${packagesLineOf(packagesOf(manifestText))} ${packagesWhereOf(root, application)}`);
|
|
5184
5312
|
if (!("written" in application) && application.unwritten === "write_failed") status = COMMAND_STATUS.failed;
|
|
5185
5313
|
io2.print(
|
|
5186
|
-
`next: commit ${committedFilesOf(plan !== null, "written" in application)}; ${"written" in application ?
|
|
5314
|
+
`next: commit ${committedFilesOf(plan !== null, "written" in application)}; ${"written" in application ? KEEP_PACKAGES_CLAUSE : COPY_PACKAGES_CLAUSE}and, where the project holds the registrar, pin the version in its instance file and run validate with kind library.`
|
|
5187
5315
|
);
|
|
5188
5316
|
printAfterInterrupt(io2, afterInterrupt);
|
|
5189
5317
|
return status;
|
|
@@ -5772,7 +5900,7 @@ var startUpgradeCheck = (io2, subcommand, named, running) => {
|
|
|
5772
5900
|
};
|
|
5773
5901
|
|
|
5774
5902
|
// src/turnzero_cloud_command/provision.ts
|
|
5775
|
-
import { basename as basename3 } from "node:path";
|
|
5903
|
+
import { basename as basename3, dirname as dirname5, join as join8 } from "node:path";
|
|
5776
5904
|
var PROVISION_OPTIONS = Object.freeze([
|
|
5777
5905
|
{
|
|
5778
5906
|
name: PROVISION_OPTION.application,
|
|
@@ -5798,6 +5926,7 @@ var PROVISION_CREDENTIAL = Object.freeze({
|
|
|
5798
5926
|
aGrant: "a secret grant",
|
|
5799
5927
|
call: "the submit_manifest call naming local_run",
|
|
5800
5928
|
mintedToken: true,
|
|
5929
|
+
emptyPipeRefused: true,
|
|
5801
5930
|
bare: `with nothing piped and neither token, it keeps a verifier and prints the challenge and the submit_manifest call naming local_run and ${CODE_CHALLENGE_MEMBER}, and a grant piped is presented with that verifier`
|
|
5802
5931
|
});
|
|
5803
5932
|
var NOTHING_REMINTED = "Nothing was re-minted, and the environment file is as it stood.";
|
|
@@ -5824,7 +5953,28 @@ var provisionOptionsOf = (call, cwd, stands = () => false) => {
|
|
|
5824
5953
|
if (!environmentFileName(basename3(envFile))) return { refusal: envFileNameRefusalOf(stands(envFile)) };
|
|
5825
5954
|
return { application, envFile, origin: origin.origin };
|
|
5826
5955
|
};
|
|
5956
|
+
var APPLICATION_FOLDER_MARKS = Object.freeze(["package.json", "manifest.json"]);
|
|
5957
|
+
var outsideApplicationWarningOf = (folder) => `warning: ${folder} holds neither ${APPLICATION_FOLDER_MARKS[0]} nor ${APPLICATION_FOLDER_MARKS[1]}, so it may not be the application's folder, and the environment file was written there all the same. Where the application stands in another folder, move the file into that folder: a local run reads the environment file from the folder it runs in.`;
|
|
5958
|
+
var outsideApplicationFolder = (io2, envFile) => APPLICATION_FOLDER_MARKS.every((mark) => {
|
|
5959
|
+
try {
|
|
5960
|
+
return io2.stat(join8(dirname5(envFile), mark))?.folder !== false;
|
|
5961
|
+
} catch {
|
|
5962
|
+
return false;
|
|
5963
|
+
}
|
|
5964
|
+
});
|
|
5965
|
+
var PROVISION_NEXT_HEAD = "next: ";
|
|
5966
|
+
var nextRunLineOf = (io2, origin, envFile) => machineLineOf(
|
|
5967
|
+
commandLineOf(origin, RUN_SUBCOMMAND, {
|
|
5968
|
+
...origin !== DEFAULT_COMMAND_ORIGIN ? { origin } : {},
|
|
5969
|
+
...envFile !== void 0 ? { quoted: [[RUN_OPTION.envFile, envFile]] } : {}
|
|
5970
|
+
}),
|
|
5971
|
+
{ windows: onWindows(io2), npx: ranThroughNpx(io2) },
|
|
5972
|
+
origin
|
|
5973
|
+
);
|
|
5974
|
+
var provisionNextLineOf = (line) => `${PROVISION_NEXT_HEAD}start the application on this machine. ${RUN_STEP_BACKGROUND}: ${line}`;
|
|
5827
5975
|
var charactersOf = (value) => `<${value.length} characters>`;
|
|
5976
|
+
var REMINT_FILE_INSTRUCTION = new RegExp(`: set [A-Z][A-Z0-9_]*${REMINT_FILE_INSTRUCTION_TAIL.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`);
|
|
5977
|
+
var writtenDetailOf = (detail, setting, envFile) => detail.replace(REMINT_FILE_INSTRUCTION, () => `, and this run wrote it into ${setting} in ${envFile}.`);
|
|
5828
5978
|
var transportText = (error) => {
|
|
5829
5979
|
const oneLine2 = (text) => text.replace(/\s+/g, " ").trim();
|
|
5830
5980
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -5888,7 +6038,7 @@ async function remint(io2, origin, credential, userAgent, application, name, ver
|
|
|
5888
6038
|
}
|
|
5889
6039
|
const answer = planeObjectOf(text);
|
|
5890
6040
|
if (status < 200 || status > 299) {
|
|
5891
|
-
const refusal =
|
|
6041
|
+
const refusal = markedRefusalOf(status, text);
|
|
5892
6042
|
if (refusal === null) return { kind: "unknown", lines: [`${PROVISION_ACTION} was answered HTTP ${status}, not by the platform.`] };
|
|
5893
6043
|
return { kind: "refused", error: refusal.error, action: answer?.action, lines: refusalLines(PROVISION_ACTION, status, refusal) };
|
|
5894
6044
|
}
|
|
@@ -5992,7 +6142,7 @@ async function provision(call, io2) {
|
|
|
5992
6142
|
io2.print(older);
|
|
5993
6143
|
return COMMAND_STATUS.nothing_started;
|
|
5994
6144
|
}
|
|
5995
|
-
return await remintAndWrite(io2, sent, agent.userAgent, { ...read2, verifier, again }, standing.text, reminted);
|
|
6145
|
+
return await remintAndWrite(io2, sent, agent.userAgent, { ...read2, verifier, again, next: nextRunLineOf(io2, sent.origin, namedEnvFile) }, standing.text, reminted);
|
|
5996
6146
|
} finally {
|
|
5997
6147
|
release();
|
|
5998
6148
|
dropWritingFile(io2, options.envFile);
|
|
@@ -6102,21 +6252,24 @@ async function remintAndWrite(io2, { application, envFile, origin }, userAgent,
|
|
|
6102
6252
|
if (facts !== null && url !== null) {
|
|
6103
6253
|
io2.print(`${envFile}: ${facts.setting}=${charactersOf(url)}, ${limit !== null ? `${LOCAL_RUN_SETTING.databaseConnectionLimit}=${limit}, ` : ""}${plain}`);
|
|
6104
6254
|
io2.print(`development database: ${oneLine(facts.roleName)} on ${facts.host}, database ${oneLine(facts.dbName)}${limit === null ? "" : `, a client pool of at most ${limit} ${limit === 1 ? "connection" : "connections"} per process`}`);
|
|
6105
|
-
if (database.kind === "answered" && typeof database.answer.detail === "string") io2.print(oneLine(database.answer.detail));
|
|
6255
|
+
if (database.kind === "answered" && typeof database.answer.detail === "string") io2.print(oneLine(writtenDetailOf(database.answer.detail, facts.setting, envFile)));
|
|
6106
6256
|
} else {
|
|
6107
6257
|
io2.print(`${envFile}: ${plain}`);
|
|
6108
6258
|
io2.print(
|
|
6109
6259
|
`no development database stands for ${application}: ${LOCAL_RUN_SETTING.databaseConnection} and ${LOCAL_RUN_SETTING.databaseConnectionLimit} were not written; submit_manifest with the database kind provisions one.`
|
|
6110
6260
|
);
|
|
6111
6261
|
}
|
|
6112
|
-
if (typeof platform.answer.detail === "string") io2.print(oneLine(platform.answer.detail));
|
|
6262
|
+
if (typeof platform.answer.detail === "string") io2.print(oneLine(writtenDetailOf(platform.answer.detail, LOCAL_RUN_SETTING.platformCredential, envFile)));
|
|
6263
|
+
if (outsideApplicationFolder(io2, envFile)) io2.print(outsideApplicationWarningOf(dirname5(envFile)));
|
|
6113
6264
|
if (facts !== null && limitCarried !== null) {
|
|
6114
6265
|
const cause = limitMember ? "the plan's database-connection-limit holds no usable quantity, which platform staff set with set_plan_quota" : "the platform that answered serves no such member yet";
|
|
6115
6266
|
io2.print(
|
|
6116
6267
|
`${envFile} holds ${facts.setting} and the platform settings this run re-minted, and no ${LOCAL_RUN_SETTING.databaseConnectionLimit}: ${PROVISION_ACTION}'s development_database answer carried ${limitCarried}, where the pool's maximum is a positive integer, so ${cause}. Until that changes the application's pool holds one connection; once it has, ${againSteps(read2.kind, read2.again)}.`
|
|
6117
6268
|
);
|
|
6269
|
+
io2.print(provisionNextLineOf(read2.next));
|
|
6118
6270
|
return COMMAND_STATUS.failed;
|
|
6119
6271
|
}
|
|
6272
|
+
io2.print(provisionNextLineOf(read2.next));
|
|
6120
6273
|
return COMMAND_STATUS.done;
|
|
6121
6274
|
}
|
|
6122
6275
|
var PROVISION_ROW = Object.freeze({
|
|
@@ -6139,14 +6292,14 @@ var PROVISION_ROW = Object.freeze({
|
|
|
6139
6292
|
},
|
|
6140
6293
|
note: [
|
|
6141
6294
|
`Run ${PROVISION_BARE_LINE_WORDS} from the folder the application runs in. With nothing piped and no token for the address, it prints a challenge and the submit_manifest call naming local_run and ${CODE_CHALLENGE_MEMBER}; your tool makes that call and runs the line it answers once, as given, before its grant expires, which presents the grant with this computer's verifier. On a computer logged in, the bare line re-mints at once.`,
|
|
6142
|
-
`Each run revokes the development credentials the application had before it. A deployed development environment holds the revoked values until it restarts, so
|
|
6295
|
+
`Each run revokes the development credentials the application had before it. A deployed development environment holds the revoked values until it restarts, so the line's last re-mint restarts it where development serves a version. Its redeploy takes both re-minted credentials from custody, the connection string and the platform credential, and does not count toward the day's deploys. Where a deploy of development is still in flight, the re-mint's answer names restart_application on development, and your tool calls it once that deploy ends. ${PROVISION_SUBCOMMAND} writes no ${PORT_SETTING}: ${RUN_SUBCOMMAND} sets it from ${RUN_OPTION.port}.`
|
|
6143
6296
|
],
|
|
6144
6297
|
afterOwnError: provisionAfterOwnError,
|
|
6145
6298
|
run: provision
|
|
6146
6299
|
});
|
|
6147
6300
|
|
|
6148
6301
|
// src/turnzero_cloud_command/scaffold.ts
|
|
6149
|
-
import { basename as basename4, dirname as
|
|
6302
|
+
import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute5, join as join9, relative as relative5, resolve as resolve11 } from "node:path";
|
|
6150
6303
|
|
|
6151
6304
|
// src/turnzero_cloud_command/scaffold_template.ts
|
|
6152
6305
|
var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
@@ -6166,7 +6319,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6166
6319
|
"",
|
|
6167
6320
|
"Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
|
|
6168
6321
|
"",
|
|
6169
|
-
`- Where the server is not connected, connect it first. The platform's page "Connect
|
|
6322
|
+
`- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
|
|
6170
6323
|
"- Before you change or deploy this service, read the server's `deploy` skill.",
|
|
6171
6324
|
"",
|
|
6172
6325
|
"## What you run here",
|
|
@@ -6329,9 +6482,9 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6329
6482
|
" }",
|
|
6330
6483
|
" send(404, { error: 'not_found' });",
|
|
6331
6484
|
" } catch (error) {",
|
|
6332
|
-
" const refused = error instanceof SyntaxError
|
|
6333
|
-
" if (
|
|
6334
|
-
" send(refused ?
|
|
6485
|
+
" const refused = error instanceof SyntaxError ? 'invalid_json' : error?.code === '23514' ? 'invalid_note' : null;",
|
|
6486
|
+
" if (refused === null) console.error(error);",
|
|
6487
|
+
" send(refused === null ? 500 : 400, { error: refused ?? 'internal' });",
|
|
6335
6488
|
" }",
|
|
6336
6489
|
" });",
|
|
6337
6490
|
"}",
|
|
@@ -6411,6 +6564,17 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6411
6564
|
" assert.deepEqual(await response.json(), { error: 'invalid_note' });",
|
|
6412
6565
|
" assert.deepEqual(await (await fetch(`${base}/notes`)).json(), []);",
|
|
6413
6566
|
"});",
|
|
6567
|
+
"",
|
|
6568
|
+
"test('POST /notes answers 400 invalid_json for a body that is not JSON, and GET /notes returns no row', async () => {",
|
|
6569
|
+
" const response = await fetch(`${base}/notes`, {",
|
|
6570
|
+
" method: 'POST',",
|
|
6571
|
+
" headers: { 'content-type': 'application/json' },",
|
|
6572
|
+
" body: '{',",
|
|
6573
|
+
" });",
|
|
6574
|
+
" assert.equal(response.status, 400);",
|
|
6575
|
+
" assert.deepEqual(await response.json(), { error: 'invalid_json' });",
|
|
6576
|
+
" assert.deepEqual(await (await fetch(`${base}/notes`)).json(), []);",
|
|
6577
|
+
"});",
|
|
6414
6578
|
""
|
|
6415
6579
|
].join("\n")]
|
|
6416
6580
|
]),
|
|
@@ -6432,7 +6596,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6432
6596
|
"",
|
|
6433
6597
|
"Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
|
|
6434
6598
|
"",
|
|
6435
|
-
`- Where the server is not connected, connect it first. The platform's page "Connect
|
|
6599
|
+
`- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
|
|
6436
6600
|
"- Before you change or deploy this service, read the server's `deploy` skill.",
|
|
6437
6601
|
"",
|
|
6438
6602
|
"## What you run here",
|
|
@@ -6551,7 +6715,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6551
6715
|
"",
|
|
6552
6716
|
"Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
|
|
6553
6717
|
"",
|
|
6554
|
-
`- Where the server is not connected, connect it first. The platform's page "Connect
|
|
6718
|
+
`- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
|
|
6555
6719
|
"- Before you change or deploy this service, read the server's `deploy` skill, and before you change its schedule, the server's `schedule` skill.",
|
|
6556
6720
|
"",
|
|
6557
6721
|
"## What you run here",
|
|
@@ -6757,7 +6921,7 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
6757
6921
|
"",
|
|
6758
6922
|
"Turn Zero Cloud serves its rules to a coding tool through its MCP server, `turnzero-cloud`, and they change with the platform, so none is repeated here.",
|
|
6759
6923
|
"",
|
|
6760
|
-
`- Where the server is not connected, connect it first. The platform's page "Connect
|
|
6924
|
+
`- Where the server is not connected, connect it first. The platform's page "Connect Claude Code or Codex" says how.`,
|
|
6761
6925
|
"- Before you change or deploy this service, read the server's `deploy` skill, and before you change its sign-in or its AI call, the server's `add-sign-in` or `use-ai-allowance` skill.",
|
|
6762
6926
|
"",
|
|
6763
6927
|
"## What you run here",
|
|
@@ -7223,9 +7387,9 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
7223
7387
|
" }",
|
|
7224
7388
|
" send(404, { error: 'not_found' });",
|
|
7225
7389
|
" } catch (error) {",
|
|
7226
|
-
" const refused = error instanceof SyntaxError
|
|
7227
|
-
" if (
|
|
7228
|
-
" send(refused ?
|
|
7390
|
+
" const refused = error instanceof SyntaxError ? 'invalid_json' : error?.code === '23514' ? 'invalid_note' : null;",
|
|
7391
|
+
" if (refused === null) console.error(error);",
|
|
7392
|
+
" send(refused === null ? 500 : 400, { error: refused ?? 'internal' });",
|
|
7229
7393
|
" }",
|
|
7230
7394
|
" });",
|
|
7231
7395
|
"}",
|
|
@@ -7394,6 +7558,17 @@ var SCAFFOLD_TEMPLATE_FILES = Object.freeze({
|
|
|
7394
7558
|
" assert.deepEqual(await (await fetch(`${base}/api/notes`, { headers: signedIn(sam) })).json(), []);",
|
|
7395
7559
|
"});",
|
|
7396
7560
|
"",
|
|
7561
|
+
"test('POST /api/notes answers 400 invalid_json for a body that is not JSON, and GET /api/notes returns no row', async () => {",
|
|
7562
|
+
" const response = await fetch(`${base}/api/notes`, {",
|
|
7563
|
+
" method: 'POST',",
|
|
7564
|
+
" headers: { 'content-type': 'application/json', ...signedIn(sam) },",
|
|
7565
|
+
" body: '{',",
|
|
7566
|
+
" });",
|
|
7567
|
+
" assert.equal(response.status, 400);",
|
|
7568
|
+
" assert.deepEqual(await response.json(), { error: 'invalid_json' });",
|
|
7569
|
+
" assert.deepEqual(await (await fetch(`${base}/api/notes`, { headers: signedIn(sam) })).json(), []);",
|
|
7570
|
+
"});",
|
|
7571
|
+
"",
|
|
7397
7572
|
"test('POST /api/ask answers under the AI allowance, and answers 503 naming why the model gave no answer: the allowance drawn, no key in place, or the provider out of capacity', async () => {",
|
|
7398
7573
|
" ai.prime({ text: 'Lunch is at noon.', usage: { input: 40, output: 12 } });",
|
|
7399
7574
|
" const ask = (question) =>",
|
|
@@ -7434,7 +7609,6 @@ var WEB_OPEN_STEP = "Open the address the deploy answers in a browser and sign i
|
|
|
7434
7609
|
var SUBMIT_NO_SETTING_STEP = `Call submit_manifest from your tool with the application and its manifest, ${APPLICATION_FOLDER}/${APPLICATION_MANIFEST}, naming no local_run, since the service reads no setting.`;
|
|
7435
7610
|
var provisionStepLineOf = (origin, windows) => machineLineOf(provisionBareLineOf({ origin, application: "<application id>" }), windows, origin);
|
|
7436
7611
|
var provisionStepOf = (origin, windows) => `Call submit_manifest from your tool with the application and its manifest, ${APPLICATION_FOLDER}/${APPLICATION_MANIFEST}, naming no local_run. Then run ${freshLineSteps2("the provision line below")}, all in ${APPLICATION_FOLDER}/. The provision line: ${provisionStepLineOf(origin, windows)}`;
|
|
7437
|
-
var RUN_STEP_BACKGROUND = "The line runs until the application ends, so start it as a background process and read its lines";
|
|
7438
7612
|
var SCHEDULED_JOB_SCHEDULE = "heartbeat";
|
|
7439
7613
|
var ENTRY_PAGES = Object.freeze({
|
|
7440
7614
|
database: Object.freeze({ follow: "the guide Add a database", page: "/cloud/guides/add-a-database/" }),
|
|
@@ -7540,6 +7714,7 @@ var templatesDescribed = () => [
|
|
|
7540
7714
|
`The template to write, one of ${andListed(SCAFFOLD_TEMPLATES)}, ${SCAFFOLD_DEFAULT_TEMPLATE} where absent.`,
|
|
7541
7715
|
...SCAFFOLD_TEMPLATES.map((template) => `The ${template} template is ${TEMPLATE_FACTS[template].is}.`)
|
|
7542
7716
|
].join(" ");
|
|
7717
|
+
var OTHER_TYPE_WAY_ON = "For a type outside these, name the nearest kind, then change the written migration and routes: the files are your service's own.";
|
|
7543
7718
|
var SCAFFOLD_OPTIONS = Object.freeze([
|
|
7544
7719
|
{
|
|
7545
7720
|
name: SCAFFOLD_OPTION.template,
|
|
@@ -7563,7 +7738,7 @@ var SCAFFOLD_OPTIONS = Object.freeze([
|
|
|
7563
7738
|
name: SCAFFOLD_OPTION.fields,
|
|
7564
7739
|
value: "<list>",
|
|
7565
7740
|
required: false,
|
|
7566
|
-
description: `Under ${RESOURCE_TEMPLATES_NAMED}, the resource's columns: fields separated by commas, each a name, a colon, and a kind, such as "name:text,message:text", written between double quotes. A name is an ASCII lower-case letter, then ASCII lower-case letters, digits, and underscores, at most 30 characters. It is never a column the migration writes itself: ${perResourceTemplate((kept2) => kept2.columns)}. A kind is ${orListed(SCAFFOLD_FIELD_KINDS)}. Every column is required, and a list holds one to ${SCAFFOLD_FIELDS_MAX} fields. Where absent, the service keeps one text column, title. A template that keeps no resource refuses it.`
|
|
7741
|
+
description: `Under ${RESOURCE_TEMPLATES_NAMED}, the resource's columns: fields separated by commas, each a name, a colon, and a kind, such as "name:text,message:text", written between double quotes. A name is an ASCII lower-case letter, then ASCII lower-case letters, digits, and underscores, at most 30 characters. It is never a column the migration writes itself: ${perResourceTemplate((kept2) => kept2.columns)}. A kind is ${orListed(SCAFFOLD_FIELD_KINDS)}. ${OTHER_TYPE_WAY_ON} Every column is required, and a list holds one to ${SCAFFOLD_FIELDS_MAX} fields. Where absent, the service keeps one text column, title. A template that keeps no resource refuses it.`
|
|
7567
7742
|
},
|
|
7568
7743
|
{
|
|
7569
7744
|
name: SCAFFOLD_OPTION.path,
|
|
@@ -7645,8 +7820,9 @@ var FIELD_REFUSAL = Object.freeze({
|
|
|
7645
7820
|
nameForm: `${SCAFFOLD_OPTION.fields} takes each field's name as an ASCII lower-case letter, then ASCII lower-case letters, digits, and underscores, at most 30 characters, such as due_date, and ${FIELD_KINDS_CLAUSE}. ${NOTHING_WRITTEN2}`,
|
|
7646
7821
|
/** A name of SCAFFOLD_FIELD_RESERVED. */
|
|
7647
7822
|
reservedName: reservedFieldRefusal(SCAFFOLD_FIELD_RESERVED),
|
|
7648
|
-
/** A kind outside SCAFFOLD_FIELD_KINDS
|
|
7649
|
-
|
|
7823
|
+
/** A kind outside SCAFFOLD_FIELD_KINDS, with the way on for another type
|
|
7824
|
+
* (#4572). */
|
|
7825
|
+
kindUnknown: `${SCAFFOLD_OPTION.fields} takes each field's kind after its name and a colon, ${FIELD_KINDS_CLAUSE}, such as count:integer. ${OTHER_TYPE_WAY_ON} ${NOTHING_WRITTEN2}`,
|
|
7650
7826
|
/** Two fields under one name. */
|
|
7651
7827
|
duplicate: `${SCAFFOLD_OPTION.fields} takes each field's name once, and two fields of the list share a name, ${FIELD_KINDS_CLAUSE} after each: name each field once. ${NOTHING_WRITTEN2}`
|
|
7652
7828
|
});
|
|
@@ -8047,18 +8223,18 @@ var ownStage = (io2, stage, files) => {
|
|
|
8047
8223
|
const folders = new Set([...texts.keys()].flatMap((path) => path.split("/").slice(0, -1).map((_, index, segments) => segments.slice(0, index + 1).join("/"))));
|
|
8048
8224
|
const own = (at, prefix) => (io2.listFolder(at) ?? []).every((name) => {
|
|
8049
8225
|
const path = prefix === "" ? name : `${prefix}/${name}`;
|
|
8050
|
-
const standing = io2.stat(
|
|
8226
|
+
const standing = io2.stat(join9(at, name));
|
|
8051
8227
|
if (standing === null) return false;
|
|
8052
|
-
if (standing.folder) return folders.has(path) && own(
|
|
8228
|
+
if (standing.folder) return folders.has(path) && own(join9(at, name), path);
|
|
8053
8229
|
const text = texts.get(path);
|
|
8054
|
-
return text !== void 0 && ownFile(io2,
|
|
8230
|
+
return text !== void 0 && ownFile(io2, join9(at, name), standing.size, path, text);
|
|
8055
8231
|
});
|
|
8056
8232
|
return own(stage, "");
|
|
8057
8233
|
};
|
|
8058
|
-
var danglingAt = (io2, folder, name) => io2.stat(
|
|
8234
|
+
var danglingAt = (io2, folder, name) => io2.stat(join9(folder, name)) === null && (io2.listFolder(folder) ?? []).some((held) => held.toLowerCase() === name.toLowerCase() && io2.stat(join9(folder, held)) === null);
|
|
8059
8235
|
var standsAt = (io2, folder, name) => {
|
|
8060
8236
|
try {
|
|
8061
|
-
return io2.stat(
|
|
8237
|
+
return io2.stat(join9(folder, name)) !== null || danglingAt(io2, folder, name);
|
|
8062
8238
|
} catch {
|
|
8063
8239
|
return true;
|
|
8064
8240
|
}
|
|
@@ -8066,31 +8242,35 @@ var standsAt = (io2, folder, name) => {
|
|
|
8066
8242
|
var noFolderSentence = (name, where) => `Something that is no folder stands at ${name} in the project's root, where ${where}: move it, then run the line again. ${NOTHING_WRITTEN2}`;
|
|
8067
8243
|
var scaffoldLinkSentence = (what) => `${what} stands on a link that leads outside the project's root, and the scaffold never writes through one. Put a folder in the link's place, or remove it, then run the line again. ${NOTHING_WRITTEN2}`;
|
|
8068
8244
|
var listOf = (value) => value === null ? [] : Array.isArray(value) ? value : [value];
|
|
8245
|
+
var BRING_PAGE = "/cloud/guides/bring-an-existing-application/";
|
|
8246
|
+
var BRING_FOLLOW = "the guide Bring an existing application";
|
|
8247
|
+
var STANDS_DEPLOY_LINE = `Where ${APPLICATION_FOLDER}/ holds an application, deploy it: call deploy from your tool and run the line it answers in ${APPLICATION_FOLDER}/, as ${BRING_FOLLOW}, read_documentation's page ${BRING_PAGE}, says.`;
|
|
8069
8248
|
var applicationStandsLines = (template, takeLines) => {
|
|
8070
8249
|
const { takes } = TEMPLATE_FACTS[template];
|
|
8071
8250
|
const lines = listOf(takeLines);
|
|
8072
8251
|
const first = `The project's root holds ${APPLICATION_FOLDER}/ with files in it, so an application stands there, and ${SCAFFOLD_SUBCOMMAND} starts a new application and writes over none. ${NOTHING_WRITTEN2}`;
|
|
8073
|
-
if (takes.length === 0 || lines.length !== takes.length) return [first];
|
|
8252
|
+
if (takes.length === 0 || lines.length !== takes.length) return [first, STANDS_DEPLOY_LINE];
|
|
8074
8253
|
return [
|
|
8075
8254
|
first,
|
|
8076
8255
|
...takes.map(
|
|
8077
8256
|
(take, index) => `To add the ${take.entry} package to the application that stands, run this line at the project's root, then follow ${take.follow}, read_documentation's page ${take.page}: ${lines[index]}`
|
|
8078
|
-
)
|
|
8257
|
+
),
|
|
8258
|
+
STANDS_DEPLOY_LINE
|
|
8079
8259
|
];
|
|
8080
8260
|
};
|
|
8081
8261
|
var STAGE_NOT_OWN = `Something that is not this run's own stands at ${STAGE_FOLDER} in the project's root, the name the scaffold writes under, an earlier run's files under another template or another resource name among it: move or remove ${STAGE_FOLDER}, then run the line again. ${NOTHING_WRITTEN2}`;
|
|
8082
8262
|
var folderRefusalOf = (io2, options, files, takeLines) => {
|
|
8083
8263
|
const { root } = options;
|
|
8084
|
-
for (let at = root; ; at =
|
|
8264
|
+
for (let at = root; ; at = dirname6(at)) {
|
|
8085
8265
|
const standing2 = io2.stat(at);
|
|
8086
|
-
const what = standing2 !== null ? standing2.folder ? null : "a file" : danglingAt(io2,
|
|
8266
|
+
const what = standing2 !== null ? standing2.folder ? null : "a file" : danglingAt(io2, dirname6(at), basename4(at)) ? "a link whose target does not stand" : null;
|
|
8087
8267
|
if (what !== null) {
|
|
8088
8268
|
return [`${options.named ? `${SCAFFOLD_OPTION.path} names ${what}, or a place under one` : `The folder the line runs in is ${what}, or stands under one`}, where the project's root stands or is made. ${NOTHING_WRITTEN2}`];
|
|
8089
8269
|
}
|
|
8090
|
-
if (standing2 !== null ||
|
|
8270
|
+
if (standing2 !== null || dirname6(at) === at) break;
|
|
8091
8271
|
}
|
|
8092
|
-
const app =
|
|
8093
|
-
const stage =
|
|
8272
|
+
const app = join9(root, APPLICATION_FOLDER);
|
|
8273
|
+
const stage = join9(root, STAGE_FOLDER);
|
|
8094
8274
|
const notOwn = STAGE_NOT_OWN;
|
|
8095
8275
|
const standing = io2.stat(app);
|
|
8096
8276
|
if (standing === null ? danglingAt(io2, root, APPLICATION_FOLDER) : !standing.folder) return [noFolderSentence(APPLICATION_FOLDER, "the scaffold writes the application")];
|
|
@@ -8156,8 +8336,9 @@ var applicationStateOf = (io2, app) => {
|
|
|
8156
8336
|
}
|
|
8157
8337
|
};
|
|
8158
8338
|
var standsWayOn = (template, takeLines) => {
|
|
8159
|
-
const
|
|
8160
|
-
return
|
|
8339
|
+
const takes = applicationStandsLines(template, takeLines).slice(1, -1);
|
|
8340
|
+
return `${takes.length > 0 ? `. ${takes.join("\n")}` : `: ${SCAFFOLD_SUBCOMMAND} starts a new application and writes over none.`}
|
|
8341
|
+
${STANDS_DEPLOY_LINE}`;
|
|
8161
8342
|
};
|
|
8162
8343
|
var stoppedCloseOf = (state, template, takeLines) => {
|
|
8163
8344
|
const before = `before ${APPLICATION_FOLDER}/ was written`;
|
|
@@ -8198,8 +8379,8 @@ var nextStepLines = (origin, windows = LINES_IN_WINDOWS_FORM, left = NEITHER_LEF
|
|
|
8198
8379
|
var writeTemplate = (io2, stage, app, files) => {
|
|
8199
8380
|
io2.remove(stage);
|
|
8200
8381
|
for (const [path, text] of files) {
|
|
8201
|
-
const at =
|
|
8202
|
-
io2.makeFolder(
|
|
8382
|
+
const at = join9(stage, ...path.split("/"));
|
|
8383
|
+
io2.makeFolder(dirname6(at));
|
|
8203
8384
|
io2.writeFile(at, text);
|
|
8204
8385
|
}
|
|
8205
8386
|
const standing = io2.stat(app);
|
|
@@ -8211,7 +8392,7 @@ var writeTemplate = (io2, stage, app, files) => {
|
|
|
8211
8392
|
};
|
|
8212
8393
|
var writeRootSentence = (io2, root) => {
|
|
8213
8394
|
try {
|
|
8214
|
-
io2.writeNewFile(
|
|
8395
|
+
io2.writeNewFile(join9(root, TOOLS_FILE), `${ROOT_SENTENCE}
|
|
8215
8396
|
`);
|
|
8216
8397
|
return "written";
|
|
8217
8398
|
} catch (error) {
|
|
@@ -8268,8 +8449,8 @@ async function scaffold(call, io2) {
|
|
|
8268
8449
|
const { takes } = TEMPLATE_FACTS[template];
|
|
8269
8450
|
const defaulted = call.named[SCAFFOLD_OPTION.template] === void 0;
|
|
8270
8451
|
const files = writtenFilesOf(template, options.names, options.fields);
|
|
8271
|
-
const app =
|
|
8272
|
-
const stage =
|
|
8452
|
+
const app = join9(root, APPLICATION_FOLDER);
|
|
8453
|
+
const stage = join9(root, STAGE_FOLDER);
|
|
8273
8454
|
const takeLines = takes.map((take) => machineLineOf(libraryTakeLineOf({ origin, entry: take.entry }), { windows: io2.windows ?? LINES_IN_WINDOWS_FORM, npx: ranThroughNpx(io2) }, origin));
|
|
8274
8455
|
let reached = "checks";
|
|
8275
8456
|
const stateNow = () => {
|
|
@@ -8298,7 +8479,7 @@ async function scaffold(call, io2) {
|
|
|
8298
8479
|
held = found;
|
|
8299
8480
|
} else {
|
|
8300
8481
|
for (const take of takes) {
|
|
8301
|
-
const copy =
|
|
8482
|
+
const copy = join9(app, "lib", take.entry);
|
|
8302
8483
|
const planOf = (library) => writesOver(library, copy) ? { refusal: LIBRARY_OVER_APPLICATION } : { application: app, copy, declared: null, declaredAt: null };
|
|
8303
8484
|
const read2 = await readTake(io2, { entry: take.entry, root, named: options.named, origin }, agent.userAgent, planOf, checks.length > 0);
|
|
8304
8485
|
if ("lines" in read2) return refuse(...read2.lines);
|
|
@@ -8822,44 +9003,79 @@ var usageLineOf = (subcommand) => {
|
|
|
8822
9003
|
return `${pipedHead(subcommand)}${SERVED} ${[namedWithActs(subcommand), ...required, ...optional].join(" ")}`;
|
|
8823
9004
|
};
|
|
8824
9005
|
var usageHeadOf = (version) => `${TURNZERO_CLOUD_PACKAGE} ${version}: the Turn Zero Cloud command, installed from the public npm registry by your platform's tag, latest on production and next on development.`;
|
|
8825
|
-
var
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8829
|
-
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
|
|
8833
|
-
|
|
8834
|
-
|
|
8835
|
-
|
|
8836
|
-
const
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8843
|
-
|
|
8844
|
-
|
|
8845
|
-
}
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8849
|
-
|
|
8850
|
-
|
|
8851
|
-
|
|
8852
|
-
|
|
8853
|
-
|
|
8854
|
-
}
|
|
8855
|
-
|
|
9006
|
+
var unreadNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential === null && subcommand.reads === void 0).map((subcommand) => subcommand.name);
|
|
9007
|
+
var unattendedNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential?.mintedToken === true).map((subcommand) => subcommand.name);
|
|
9008
|
+
var computerOnlyNames = () => SUBCOMMANDS.filter((subcommand) => subcommand.credential?.computerToken === true).map((subcommand) => subcommand.name);
|
|
9009
|
+
var sectionLinesOf = (subcommand) => [
|
|
9010
|
+
`${namedWithActs(subcommand)}: ${subcommand.summary}`,
|
|
9011
|
+
"",
|
|
9012
|
+
"Options:",
|
|
9013
|
+
...subcommand.options.flatMap((option) => [` ${shown(option)}${option.required ? " (required)" : ""}`, ` ${option.description}`]),
|
|
9014
|
+
""
|
|
9015
|
+
];
|
|
9016
|
+
var pipedLinesOf = (subcommand) => {
|
|
9017
|
+
const credential = subcommand.credential;
|
|
9018
|
+
if (credential === null || credential.piped === false) return [];
|
|
9019
|
+
return [
|
|
9020
|
+
` ${subcommand.name} reads ${credential.aGrant} from standard input, as the line ${credential.call} answers pipes it in${credential.mintedToken ? `; and otherwise a minted token from ${MINTED_TOKEN_VARIABLE} where it is set, or this computer's token from its credential file` : credential.computerToken === true ? `; and otherwise this computer's token from its credential file, never a minted token from ${MINTED_TOKEN_VARIABLE}, whose being set refuses the run` : ""}${credential.bare !== void 0 ? `; ${credential.bare}` : ""}.`
|
|
9021
|
+
];
|
|
9022
|
+
};
|
|
9023
|
+
var readsLinesOf = (subcommand) => subcommand.reads !== void 0 ? [` ${subcommand.name} reads ${subcommand.reads}.`] : [];
|
|
9024
|
+
var noCredentialLine = () => {
|
|
9025
|
+
const unread = unreadNames();
|
|
9026
|
+
return ` The command takes no credential in its arguments, and ${listed(unread)} ${unread.length === 1 ? "reads" : "read"} none.`;
|
|
9027
|
+
};
|
|
9028
|
+
var mintedTokenVariableLine = () => ` ${MINTED_TOKEN_VARIABLE} under ${listed(unattendedNames())}, a minted token for a tool connected by a minted token or an unattended run, set for the one line from where the tool keeps it or from your CI's secret store. Where it is set, it wins over this computer's credential file.`;
|
|
9029
|
+
var originVariableLine = () => {
|
|
9030
|
+
const computerOnly = computerOnlyNames();
|
|
9031
|
+
return ` ${ORIGIN_VARIABLE} under ${listed(unattendedNames())}, in an unattended run, the platform's address the minted token is sent to where the line names no ${DEPLOY_OPTION.origin}. Under the token, a ${DEPLOY_OPTION.origin} the line names must be that address, or ${DEFAULT_COMMAND_ORIGIN} where this variable is unset. Where ${MINTED_TOKEN_VARIABLE} is not set and the line names no ${DEPLOY_OPTION.origin}, this variable names the address whose token the command reads from this computer's credential file${computerOnly.length > 0 ? `, under ${listed(computerOnly)} too where no grant is piped` : ""}. A run under a grant does not read it.`;
|
|
9032
|
+
};
|
|
9033
|
+
var USER_AGENT_LINE = ` ${USER_AGENT_VARIABLE} text appended to the command's user agent on each request.`;
|
|
9034
|
+
var ownVariableLinesOf = (subcommand) => (subcommand.variables ?? []).map(([name, does]) => ` ${name} under ${subcommand.name}, ${does}`);
|
|
9035
|
+
var statusLineOf = (status, meaning, clauses, also) => ` ${COMMAND_STATUS[status]} ${meaning}${clauses.length > 0 ? `: ${clauses.join("; ")}` : ""}.${also !== void 0 ? ` ${also}` : ""}`;
|
|
9036
|
+
var noteLinesOf2 = (subcommand) => (subcommand.note ?? []).flatMap((paragraph) => ["", paragraph]);
|
|
9037
|
+
var usageBlockOf = (subcommandHelp) => [
|
|
9038
|
+
"Usage:",
|
|
9039
|
+
...SUBCOMMANDS.map((subcommand) => ` ${usageLineOf(subcommand)}`),
|
|
9040
|
+
` ${SERVED} --version`,
|
|
9041
|
+
` ${SERVED} --help`,
|
|
9042
|
+
...subcommandHelp ? [` ${SERVED} <subcommand> --help`] : []
|
|
9043
|
+
];
|
|
9044
|
+
var sharedSentenceLines = () => [WINDOWS_SENTENCE, "", NPX_FORM_SENTENCE, "", QUOTED_PATH_SENTENCE];
|
|
9045
|
+
var SUBCOMMAND_HELP_SENTENCE = `Run ${SERVED} <subcommand> --help for that subcommand's options, credential, variables, statuses, and notes.`;
|
|
9046
|
+
var SHARED_HELP_SENTENCE = `Run ${SERVED} --help for every subcommand and the sentences they share: a line's Windows form, its npx form, and a path between double quotes.`;
|
|
9047
|
+
var shortUsageText = (version = commandVersion()) => [
|
|
9048
|
+
usageHeadOf(version),
|
|
9049
|
+
"",
|
|
9050
|
+
...usageBlockOf(true),
|
|
9051
|
+
"",
|
|
9052
|
+
...sharedSentenceLines(),
|
|
9053
|
+
"",
|
|
9054
|
+
"Statuses:",
|
|
9055
|
+
...STATUS_MEANINGS.map(([status, meaning, also]) => statusLineOf(status, meaning, [], also)),
|
|
9056
|
+
"",
|
|
9057
|
+
"Subcommands:",
|
|
9058
|
+
...SUBCOMMANDS.map((subcommand) => ` ${namedWithActs(subcommand)}: ${subcommand.summary}`),
|
|
9059
|
+
"",
|
|
9060
|
+
SUBCOMMAND_HELP_SENTENCE
|
|
9061
|
+
].join("\n");
|
|
9062
|
+
var subcommandHelpOf = (subcommand, version = commandVersion()) => {
|
|
9063
|
+
const lines = [usageHeadOf(version), "", "Usage:", ` ${usageLineOf(subcommand)}`, "", ...sectionLinesOf(subcommand)];
|
|
9064
|
+
lines.push("Input:", ...pipedLinesOf(subcommand), ...readsLinesOf(subcommand), noCredentialLine(), "", "Environment variables:", ...ownVariableLinesOf(subcommand));
|
|
9065
|
+
if (unattendedNames().includes(subcommand.name)) lines.push(mintedTokenVariableLine());
|
|
9066
|
+
if (unattendedNames().length > 0 && (unattendedNames().includes(subcommand.name) || computerOnlyNames().includes(subcommand.name))) lines.push(originVariableLine());
|
|
9067
|
+
lines.push(USER_AGENT_LINE, "", "Statuses:");
|
|
8856
9068
|
for (const [status, meaning, also] of STATUS_MEANINGS) {
|
|
8857
|
-
const
|
|
8858
|
-
|
|
9069
|
+
const clause = subcommand.statuses[status];
|
|
9070
|
+
if (clause !== void 0 || also !== void 0) lines.push(statusLineOf(status, meaning, clause !== void 0 ? [`under ${subcommand.name}, ${clause}`] : [], also));
|
|
8859
9071
|
}
|
|
8860
|
-
|
|
9072
|
+
lines.push(...noteLinesOf2(subcommand), "", SHARED_HELP_SENTENCE);
|
|
8861
9073
|
return lines.join("\n");
|
|
8862
9074
|
};
|
|
9075
|
+
var subcommandHelpText = (name, version = commandVersion()) => {
|
|
9076
|
+
const subcommand = SUBCOMMANDS.find((entry) => entry.name === name);
|
|
9077
|
+
return subcommand === void 0 ? null : subcommandHelpOf(subcommand, version);
|
|
9078
|
+
};
|
|
8863
9079
|
var grantArgumentSentence = () => "An argument holds a grant, and the command never takes a grant from its arguments: it reads the grant from standard input, as the line your tool's call answers pipes it in. Run the line the call answered as given. Nothing was sent and nothing was started.";
|
|
8864
9080
|
var tokenArgumentSentence = () => `An argument holds a minted token, and the command never takes a credential from its arguments: it reads a minted token from ${MINTED_TOKEN_VARIABLE} alone, which a tool connected by a minted token, or an unattended run, sets for the one line from where the tool keeps the token or from your CI's secret store. Nothing was sent and nothing was started.`;
|
|
8865
9081
|
var NOTHING_STARTED2 = "Nothing was started.";
|
|
@@ -8880,21 +9096,21 @@ var valuePlacesOf = (argv2) => {
|
|
|
8880
9096
|
}
|
|
8881
9097
|
return places;
|
|
8882
9098
|
};
|
|
9099
|
+
var noSubcommandSentence = (first) => `${first === void 0 ? "No subcommand was named" : `The first argument is not a subcommand of ${TURNZERO_CLOUD_PACKAGE}`}: the subcommands are ${SUBCOMMANDS.map((entry) => entry.name).join(", ")}. ${NOTHING_STARTED2}`;
|
|
8883
9100
|
var parseArguments = (argv2, onWindows2 = ON_WINDOWS) => {
|
|
8884
9101
|
if (argv2.some((argument) => argument.includes(UPLOAD_GRANT_PREFIX))) return { kind: "refused", sentence: grantArgumentSentence() };
|
|
8885
9102
|
if (argv2.some((argument) => argument.includes(MINTED_TOKEN_PREFIX))) return { kind: "refused", sentence: tokenArgumentSentence() };
|
|
8886
9103
|
const values = valuePlacesOf(argv2);
|
|
8887
9104
|
const standsAlone = (words) => argv2.some((argument, index) => words.includes(argument) && !values.has(index));
|
|
8888
|
-
if (standsAlone(["--help", "-h"])) return { kind: "help" };
|
|
8889
|
-
if (standsAlone(["--version"])) return { kind: "version" };
|
|
8890
9105
|
const [first, ...afterName] = argv2;
|
|
8891
9106
|
const subcommand = SUBCOMMANDS.find((entry) => entry.name === first);
|
|
8892
|
-
if (
|
|
8893
|
-
return {
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
};
|
|
9107
|
+
if (standsAlone(["--help", "-h"])) {
|
|
9108
|
+
if (subcommand !== void 0) return { kind: "help", subcommand };
|
|
9109
|
+
if (first.startsWith("-")) return { kind: "help" };
|
|
9110
|
+
return { kind: "refused", sentence: noSubcommandSentence(first) };
|
|
8897
9111
|
}
|
|
9112
|
+
if (standsAlone(["--version"])) return { kind: "version" };
|
|
9113
|
+
if (subcommand === void 0) return { kind: "refused", sentence: noSubcommandSentence(first) };
|
|
8898
9114
|
let act = null;
|
|
8899
9115
|
let rest = afterName;
|
|
8900
9116
|
if (subcommand.acts !== void 0) {
|
|
@@ -8954,7 +9170,7 @@ async function runGuarded(argv2, io2) {
|
|
|
8954
9170
|
}
|
|
8955
9171
|
const parsed = parseArguments(argv2, io2.windows ?? ON_WINDOWS);
|
|
8956
9172
|
if (parsed.kind === "help") {
|
|
8957
|
-
io2.print(
|
|
9173
|
+
io2.print((parsed.subcommand === void 0 ? null : subcommandHelpText(parsed.subcommand.name, version)) ?? shortUsageText(version));
|
|
8958
9174
|
return COMMAND_STATUS.done;
|
|
8959
9175
|
}
|
|
8960
9176
|
if (parsed.kind === "version") {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turnzero",
|
|
3
|
-
"version": "0.18.1",
|
|
3
|
+
"version": "0.18.3-next.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The Turn Zero Cloud command, which an AI coding tool runs on your machine to deploy an application, store a secret, upload a file, download an export, take a library entry, write a local run's settings, start the application locally, and scaffold a new service from a template.",
|
|
6
6
|
"license": "Apache-2.0",
|