@runcofactor/cli 0.46.0 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-IJTIWAY6.js → chunk-BYKQ5N5L.js} +65 -21
- package/dist/chunk-BYKQ5N5L.js.map +1 -0
- package/dist/{chunk-E6K4PAID.js → chunk-SQXLD7QP.js} +2 -2
- package/dist/chunk-SQXLD7QP.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/{landing-NJZS5JDT.js → landing-3TY7TLVZ.js} +2 -2
- package/dist/{sandbox-OIC6CDHG.js → sandbox-5Y4HALKW.js} +160 -41
- package/dist/sandbox-5Y4HALKW.js.map +1 -0
- package/dist/{skill-G2654GLA.js → skill-A4MN7UIT.js} +327 -14
- package/dist/skill-A4MN7UIT.js.map +1 -0
- package/dist/{workflow-NTSXBZR3.js → workflow-QRCK7U6G.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-E6K4PAID.js.map +0 -1
- package/dist/chunk-IJTIWAY6.js.map +0 -1
- package/dist/sandbox-OIC6CDHG.js.map +0 -1
- package/dist/skill-G2654GLA.js.map +0 -1
- /package/dist/{landing-NJZS5JDT.js.map → landing-3TY7TLVZ.js.map} +0 -0
- /package/dist/{workflow-NTSXBZR3.js.map → workflow-QRCK7U6G.js.map} +0 -0
|
@@ -56,16 +56,135 @@ var environments_default = "An environment (a \"flavor\") is the same sandbox \u
|
|
|
56
56
|
var seeds_and_actions_default = "An action is a script with a typed input and output schema. It is the one\nway data gets into a sandbox \u2014 seeds call it, and so do tests and\n`cofactor sandbox run`. Every environment's `seeds` list is\njust an ordered sequence of action (and emulator operation) calls. For\nthe script contract (`INPUT_<NAME>`, the result line), see the `upload`\ntopic's worked example; it is the same for GitHub-delivered apps.\n\nWHAT TO BUILD FIRST\n\n 1. One persona per role your tests need. The example app has three\n roles, so it declares `owner`, `member` and `viewer`. A persona in\n the definition is only a login (prepare makes it work \u2014 see\n `personas`); its role comes from a seed action such as `add-member`,\n or from prepare itself when the persona is an account your app\n already has with the right role. With one persona per role, tests can\n sign in as each and check what that role may and may not do.\n\n 2. One environment per plan or feature-flag combination your tests care\n about: `default`, `free-plan`, `beta-features`. Set each with a seed\n action that goes through your app's admin API or database (the\n example's `set-plan`), or with the environment's `env:` when your app\n reads the flag from an environment variable (the example's\n `FEATURE_FLAGS`; see the `env` topic for which processes see it).\n Params are per definition, not per environment, so they can't vary a\n flag between environments. See the `environments` topic.\n\n 3. Actions for the few core objects nearly every test needs, each taking\n `as: <persona>`: in the example, `create-workspace`, `add-member`,\n `create-project`, `create-task`. Keep the shared seeds thin; tests\n create the rest themselves with these actions.\n\n 4. If your app stores files, declare `object_storage` and seed the few\n files every test needs (a logo, an import template) with an action;\n see the `object-storage` topic.\n\nSEEDS VS. ACTIONS A TEST CALLS ITSELF\n\n Seeds are shared, thin, generic base data: the handful of things nearly\n every test needs before it can start \u2014 a workspace and its members, one\n per role. A shared seed stays thin and generic; it does not try to\n anticipate every test's needs.\n\n An action a test calls itself is data specific to that one test. If a\n test's job is \"archive a project,\" the project it archives should not\n live in a shared seed \u2014 a shared seed that every test's setup depends on\n becomes something every other test can break by changing it, and it\n bloats claim time for tests that never needed it. The test creates its\n own project by calling `create-project`, then operates on the id it got\n back.\n\n Rule of thumb: if removing this data would break tests that aren't about\n it, it's a seed. If only one test's own scenario needs it, it's an action\n call inside that test.\n\nWHAT MAKES A GOOD ACTION\n\n - Typed inputs and outputs. The input schema is what a test (or an\n agent) uses to call it correctly; the output schema is what the next\n step gets to chain from. Untyped free-form data defeats the ability to\n validate `${id.key}` references and CLI input ahead of time.\n - Fast. Seeds run once per version, while its snapshot is built; calls\n run inline in a test \u2014 slow seeds slow every new version's first\n claim, and slow calls slow every test.\n - Returns the ids a test needs next. `create-project` returns\n `project_id`, not just \"ok\" \u2014 that id is what the test's next action\n or assertion needs.\n - Safe to call more than once, or unique by its own input. Either the\n action is idempotent (`set-plan` re-run with the same plan changes\n nothing), or a distinct call naturally produces a distinct result\n (`create-project` with a new name makes a new project) rather than\n silently colliding with a previous call's data.\n - Explicit about who acts: the `as` persona pattern. Every action that\n performs a real business operation (creating a project, inviting a\n member) takes an explicit `as: <persona>` input naming which persona\n performs it, rather than assuming \"the admin\" or \"whoever is logged\n in.\" The script signs in as exactly that persona (its login is in\n `COFACTOR_PERSONAS`) and acts in the workspace it was given; the\n persona must already have a role there that allows the operation. So\n an action has **no hidden dependency** on some other seed having run\n first to establish who's \"in charge\" \u2014 the caller says so, in the\n input.\n - Goes through the app's own API when the data is encrypted or has side\n effects. `invite-user` calls the real invite endpoint so the\n invitation email is actually sent (and can be observed later with\n `email.sent-emails`); a raw insert would silently skip the send. The\n same goes for anything the app encrypts or hashes on write.\n - Goes through the database only for rows with no encryption and no side\n effects, and only when there is no API for it \u2014 e.g. `set-plan`\n updating a workspace's plan column, which in production only an\n internal billing process sets.\n\nANTI-PATTERNS\n\n - A giant do-everything seed. One seed step that creates a workspace,\n three members, two projects and their tasks in one script is harder to\n reuse, harder to debug when it fails partway, and forces every test\n that wants only part of it to take all of it. Break it into separate\n actions and chain them with `${id.key}`, as `default` does.\n - An action that silently acts as an admin. If an action doesn't take\n `as`, and instead assumes some fixed identity internally, a test\n writer has no way to know (or override) who actually performed the\n operation, and the action can't be reused for a test that needs a\n viewer to attempt (and be denied) the same thing.\n - Hardcoded ids. An action or seed that references a specific workspace\n or project id baked into the script defeats the whole point of\n chaining `${id.key}` \u2014 every id a later step needs should come from an\n earlier step's declared output.\n - An action that depends on seed ordering it doesn't declare. A script\n that assumes some other seed already ran (e.g. that a particular\n workspace exists) without that dependency showing up as an input is a\n hidden coupling \u2014 if the environment's seed order ever changes, the\n action breaks with no local sign of why. Take everything you need as\n an explicit input instead (as `create-project` takes `workspace_id`\n and `as`).\n - Writing output anywhere but the last `{`-starting stdout line. The\n action's result is exactly the *last* line of stdout that starts with\n `{`. Send every progress message, debugging `echo` and intermediate\n curl response to stderr \u2014 printing one to stdout by mistake silently\n corrupts the parsed output (or an earlier `{`-line gets picked up as a\n decoy if you print the wrong one last).\n\nWORKED EXAMPLE: CREATE THEN ARCHIVE A PROJECT\n\n Workspace \"Acme\" already exists (seeded by `default`'s `acme` step); the\n test supplies its own project rather than relying on a seeded one,\n because creating and archiving a project are the test's own subject.\n\n 1. Claim a sandbox on `default` and note `acme`'s output:\n cofactor sandbox claim --application \"Project Tracker\" --wait\n The claim's seed outputs include `acme.workspace_id`.\n\n 2. Create the project the test will operate on, acting as the member:\n cofactor sandbox run <id> create-project \\\n --input workspace_id=<acme.workspace_id> \\\n --input name=Temp \\\n --input as=member\n Returns `{\"project_id\": \"<id>\"}`.\n\n 3. Drive the app (via its `web`/`api` port URLs) to verify the project\n shows up wherever the product surfaces it, using the `project_id`\n from step 2 to find the right row.\n\n 4. Archive it the way the product's UI or API would \u2014 this is what the\n test is actually verifying, so it belongs in the test's own steps,\n not in a seed. Repeating it as `viewer` should be refused.\n\n 5. Destroy the sandbox:\n cofactor sandbox destroy <id>\n\n Nothing about the project this test created is shared with any other\n test, and nothing another seed does could break this test's setup.\n";
|
|
57
57
|
|
|
58
58
|
// ../content/src/sandbox-v2-guide/personas.txt
|
|
59
|
-
var personas_default = "A persona is a named login identity declared in `cofactor.sandbox.yaml`:\n personas:\n owner: { email: owner@example.test }\n member: { email: member@example.test }\n viewer: { email: viewer@example.test }\n\nDeclare one per role your tests need (see \"WHAT TO BUILD FIRST\" in\n`seeds-and-actions`). Only the email is fixed in the definition.\nEverything else about a persona is assigned per sandbox or per seed:\n\nPASSWORDS \u2014 SET BY PREPARE\n\n Each sandbox gets its own randomly generated password per persona,\n created during the `prepare` step of the claim lifecycle (see the\n `overview` topic). Those passwords never appear in the definition \u2014\n they're generated fresh for that one sandbox and handed to\n `lifecycle.prepare` as `COFACTOR_PERSONAS`, a JSON map of persona name to\n `{email, password}`. Your prepare script reads it and makes each login\n work in your app: the example app's `sandbox/personas.sh` creates or\n updates one user per persona (email + password hash), with no workspace\n and no role \u2014 just the login itself. The `upload` topic covers the\n details (prepare runs while the app is up; logins must be read per\n sign-in, not once at startup).\n\nROLES AND MEMBERSHIPS \u2014 SET BY SEEDS\n\n A persona existing (having a login) is separate from what it can do.\n Workspace membership and role are set by seed steps, not by the\n definition: `create-workspace` makes its `as` persona the owner of the\n new workspace, and `add-member` gives a persona a role in it. This split\n matters: the same persona can be \"just a login\" in one environment and\n a workspace owner in another, because that comes from which seeds\n actually ran. If a persona's email is an account your app already has\n with the right role, prepare setting its password is enough.\n\nHOW A SEED OR ACTION SIGNS IN AS A PERSONA\n\n Actions receive `COFACTOR_PERSONAS` too. An action that performs a real\n operation takes the acting persona as an input (`as`, of type\n `persona`, which arrives as `INPUT_AS` holding the persona's name),\n reads that persona's email and password from `COFACTOR_PERSONAS`, and\n signs in through the app's real login endpoint \u2014 the same way a user\n would \u2014 keeping the session cookie for its next calls:\n login=$(jq -c --arg p \"$INPUT_AS\" '.[$p] | {email, password}' <<<\"$COFACTOR_PERSONAS\")\n jar=$(mktemp)\n curl -fsS -c \"$jar\" -H 'content-type: application/json' \\\n -d \"$login\" \"$API_URL/api/auth/login\" >&2\n See the `seeds-and-actions` topic on why `as` is never assumed.\n\n An action runs inside the sandbox, so it can reach the app on\n localhost. If your app's session cookie is `Secure` (or `__Host-`\n prefixed) and your HTTP client won't send it back over plain http, call\n the app through its public port URL (here `API_URL`, from a\n `{port: api}` param) instead.\n\nWHERE LOGINS ARE SHOWN\n\n Persona logins (email + generated password) are surfaced by\n `cofactor sandbox show <id>` and by `cofactor sandbox preview` for PR\n previews. They are never printed in a PR comment \u2014 a PR comment only\n links to where they can be looked up, keeping generated passwords out of\n anything that ends up in GitHub's own history. Cofactor's test runs sign\n in with them automatically, as the application's persona of the same\n name.\n\n\nNATIVE WORKFLOW LOGIN NODES\n\n Sandboxes v2 uses `authentication.run` to mint a fresh browser session for\n the persona named by each native workflow login node. Read\n `cofactor sandbox guide v2 authentication` for the complete script contract,\n cookie and localStorage formats, and how to keep provider-login journeys\n clean and unauthenticated at the start.\n";
|
|
59
|
+
var personas_default = "A persona is a named login identity declared in `cofactor.sandbox.yaml`:\n personas:\n owner: { email: owner@example.test }\n member: { email: member@example.test }\n viewer: { email: viewer@example.test }\n\nDeclare one per role your tests need (see \"WHAT TO BUILD FIRST\" in\n`seeds-and-actions`). Only the email is fixed in the definition.\nEverything else about a persona is assigned per sandbox or per seed:\n\nPASSWORDS \u2014 SET BY PREPARE\n\n Each sandbox gets its own randomly generated password per persona,\n created during the `prepare` step of the claim lifecycle (see the\n `overview` topic). Those passwords never appear in the definition \u2014\n they're generated fresh for that one sandbox and handed to\n `lifecycle.prepare` as `COFACTOR_PERSONAS`, a JSON map of persona name to\n `{email, password}`. Your prepare script reads it and makes each login\n work in your app: the example app's `sandbox/personas.sh` creates or\n updates one user per persona (email + password hash), with no workspace\n and no role \u2014 just the login itself. The `upload` topic covers the\n details (prepare runs while the app is up; logins must be read per\n sign-in, not once at startup).\n\n If your app gates sign-in behind a first-run step (accepting terms,\n initializing the instance, verifying the email address, finishing\n onboarding), clear it in prepare for each persona, the way you set the\n password: a seed or test signing in through the real login API should\n land signed in, not on the gate.\n\nROLES AND MEMBERSHIPS \u2014 SET BY SEEDS\n\n A persona existing (having a login) is separate from what it can do.\n Workspace membership and role are set by seed steps, not by the\n definition: `create-workspace` makes its `as` persona the owner of the\n new workspace, and `add-member` gives a persona a role in it. This split\n matters: the same persona can be \"just a login\" in one environment and\n a workspace owner in another, because that comes from which seeds\n actually ran. If a persona's email is an account your app already has\n with the right role, prepare setting its password is enough.\n\nHOW A SEED OR ACTION SIGNS IN AS A PERSONA\n\n Actions receive `COFACTOR_PERSONAS` too. An action that performs a real\n operation takes the acting persona as an input (`as`, of type\n `persona`, which arrives as `INPUT_AS` holding the persona's name),\n reads that persona's email and password from `COFACTOR_PERSONAS`, and\n signs in through the app's real login endpoint \u2014 the same way a user\n would \u2014 keeping the session cookie for its next calls:\n login=$(jq -c --arg p \"$INPUT_AS\" '.[$p] | {email, password}' <<<\"$COFACTOR_PERSONAS\")\n jar=$(mktemp)\n curl -fsS -c \"$jar\" -H 'content-type: application/json' \\\n -d \"$login\" \"$API_URL/api/auth/login\" >&2\n See the `seeds-and-actions` topic on why `as` is never assumed.\n\n An action runs inside the sandbox, so it can reach the app on\n localhost. If your app's session cookie is `Secure` (or `__Host-`\n prefixed) and your HTTP client won't send it back over plain http, call\n the app through its public port URL (here `API_URL`, from a\n `{port: api}` param) instead.\n\n Keep whatever the login returns that later calls need: the session\n cookie, a bearer token from the response body, or both (some apps bind\n a bearer token to an httpOnly cookie and refuse one without the other).\n\n Every action signs in from the same address, so a seed that runs several\n actions can trip the app's login rate limit (often around 10 attempts a\n minute). If your app has one, raise it for the sandbox through the\n definition's top-level `env:` (it reaches every service) rather than\n adding retries to your actions.\n\nWHERE LOGINS ARE SHOWN\n\n Persona logins (email + generated password) are surfaced by\n `cofactor sandbox show <id>` and by `cofactor sandbox preview` for PR\n previews. They are never printed in a PR comment \u2014 a PR comment only\n links to where they can be looked up, keeping generated passwords out of\n anything that ends up in GitHub's own history. Cofactor's test runs sign\n in with them automatically, as the application's persona of the same\n name.\n\n\nNATIVE WORKFLOW LOGIN NODES\n\n Sandboxes v2 uses `authentication.run` to mint a fresh browser session for\n the persona named by each native workflow login node. Read\n `cofactor sandbox guide v2 authentication` for the complete script contract,\n cookie and localStorage formats, and how to keep provider-login journeys\n clean and unauthenticated at the start.\n";
|
|
60
60
|
|
|
61
61
|
// ../content/src/sandbox-v2-guide/authentication.txt
|
|
62
|
-
var authentication_default =
|
|
62
|
+
var authentication_default = `Native workflow login nodes use the persona declared in this sandbox definition. Declare \`authentication.run\` when your sandbox needs native persona login. Existing definitions can use personas in prepare, seeds and actions without this script; a native login without it fails with \`script_not_declared\`. Each run gets its own copy, so Cofactor asks the copy's \`authentication.run\` script to create an application session for the requested persona. The script receives the prepared copy environment and \`COFACTOR_AUTH_PERSONA\`; it returns Playwright storage state as JSON on stdout. The cookies must be for the copy's public origin (sign in through the public URL, as below), not localhost. The example needs no language runtime: a POSIX shell, \`curl\`, \`jq\` and \`awk\`:
|
|
63
|
+
|
|
64
|
+
\`\`\`yaml
|
|
65
|
+
personas:
|
|
66
|
+
admin: { email: admin@example.test }
|
|
67
|
+
member: { email: member@example.test }
|
|
68
|
+
authentication:
|
|
69
|
+
run: sandbox/auth.sh
|
|
70
|
+
\`\`\`
|
|
71
|
+
|
|
72
|
+
\`\`\`bash
|
|
73
|
+
#!/bin/sh
|
|
74
|
+
set -eu
|
|
75
|
+
|
|
76
|
+
persona=\${COFACTOR_AUTH_PERSONA:?missing persona}
|
|
77
|
+
# This persona's email and generated password; fails if either is missing.
|
|
78
|
+
login=$(printf '%s' "$COFACTOR_PERSONAS" | jq -ec --arg p "$persona" \\
|
|
79
|
+
'.[$p] | {email, password} | select((.email | type) == "string" and (.password | type) == "string")')
|
|
80
|
+
email=$(printf '%s' "$login" | jq -r .email)
|
|
81
|
+
jar=$(mktemp)
|
|
82
|
+
trap 'rm -f "$jar"' EXIT
|
|
83
|
+
|
|
84
|
+
# Sign in through the copy's public HTTPS API. This preserves Secure and
|
|
85
|
+
# __Host- cookie behavior; localhost HTTP is not suitable for those cookies.
|
|
86
|
+
printf '%s' "$login" | curl -fsS -c "$jar" -H 'content-type: application/json' \\
|
|
87
|
+
--data-binary @- "$API_URL/api/auth/login" >/dev/null
|
|
88
|
+
|
|
89
|
+
# Check the resulting identity before giving the session to the browser.
|
|
90
|
+
curl -fsS -b "$jar" "$API_URL/api/auth/me" | jq -e --arg email "$email" '.user.email == $email' >/dev/null
|
|
91
|
+
|
|
92
|
+
# Convert the cookie jar to Playwright storage-state JSON. Curl's Netscape
|
|
93
|
+
# format does not carry SameSite, so adapt this field to your app's cookie.
|
|
94
|
+
awk -F '\\t' '
|
|
95
|
+
{ httponly = "false" }
|
|
96
|
+
/^#HttpOnly_/ { sub(/^#HttpOnly_/, ""); httponly = "true" }
|
|
97
|
+
/^#/ || NF < 7 { next }
|
|
98
|
+
{ print $6 "\\t" $7 "\\t" $1 "\\t" $3 "\\t" $5 "\\t" httponly "\\t" $4 }
|
|
99
|
+
' "$jar" | jq -Rn '{
|
|
100
|
+
cookies: [inputs | split("\\t") | {
|
|
101
|
+
name: .[0], value: .[1], domain: .[2], path: .[3], expires: (.[4] | tonumber | if . == 0 then -1 else . end),
|
|
102
|
+
httpOnly: (.[5] == "true"), secure: (.[6] == "TRUE"), sameSite: "Lax"
|
|
103
|
+
}],
|
|
104
|
+
origins: []
|
|
105
|
+
}'
|
|
106
|
+
\`\`\`
|
|
107
|
+
|
|
108
|
+
If the app stores its access token in local storage, include
|
|
109
|
+
an \`origins\` entry for the exact copy origin, with \`{name, value}\` pairs in
|
|
110
|
+
\`localStorage\`. Do not print secrets to stderr, write state to a normal action,
|
|
111
|
+
or use a fixed browser profile. Cofactor validates the returned state against
|
|
112
|
+
the current copy's public origins and keeps it private to that execution.
|
|
113
|
+
|
|
114
|
+
THIRD-PARTY IDENTITY PROVIDERS
|
|
115
|
+
|
|
116
|
+
For ordinary application tests, create the app's session directly. Use the
|
|
117
|
+
sandbox-only login API, a signed test token accepted only by the copy, an SDK,
|
|
118
|
+
or the provider's emulator-backed token exchange. The auth action should
|
|
119
|
+
finish with the application's own session cookie or local-storage token. A
|
|
120
|
+
flow that tests login itself must begin clean: retain its application login
|
|
121
|
+
node with \`performLogin: false\` to select the app and environment, without
|
|
122
|
+
seeding or preloading an app session. Then navigate to the app and traverse
|
|
123
|
+
its real sign-in UI against the declared provider emulator.
|
|
124
|
+
|
|
125
|
+
Google OAuth requires interception on both sides. Cofactor intercepts the
|
|
126
|
+
browser authorization request, including an app HTTP redirect to Google,
|
|
127
|
+
and returns an emulated code to the copy's callback. The declared sandbox
|
|
128
|
+
emulator handles the app's token exchange and user-info requests. Verify
|
|
129
|
+
the complete callback and resulting app identity; an intercepted browser
|
|
130
|
+
redirect alone does not prove the server handshake works.
|
|
131
|
+
|
|
132
|
+
GOOGLE CALLBACK CONFIGURATION
|
|
133
|
+
|
|
134
|
+
Supply the current copy's callback using the app's existing configuration:
|
|
135
|
+
params:
|
|
136
|
+
api_url: { port: api }
|
|
137
|
+
env:
|
|
138
|
+
GOOGLE_CLIENT_ID: sandbox
|
|
139
|
+
GOOGLE_CLIENT_SECRET: sandbox
|
|
140
|
+
GOOGLE_REDIRECT_URI: \${params.api_url}/auth/callback
|
|
141
|
+
emulation:
|
|
142
|
+
google-oauth: {}
|
|
143
|
+
|
|
144
|
+
This is a fragment: declare the public api port, use your app's actual
|
|
145
|
+
variable names and callback path, and set reload_on_prepare: true on the
|
|
146
|
+
backend calling Google. An app whose callback comes from configuration
|
|
147
|
+
can use this without auth code changes. A hardcoded production callback
|
|
148
|
+
must become configurable. No per-copy Google Console registration is
|
|
149
|
+
needed for the emulated path; real Google login keeps Google's rules.
|
|
150
|
+
|
|
151
|
+
Do not add live: true merely to avoid service restarts. That optimization
|
|
152
|
+
requires the app to re-read the live value per request; a normal {port}
|
|
153
|
+
param with reload_on_prepare works for apps that read env at startup.
|
|
154
|
+
|
|
155
|
+
Google needs both browser authorization interception and sandbox-side
|
|
156
|
+
token/user-info emulation. Verify the app callback and a protected request,
|
|
157
|
+
not just that the browser received an emulated code. App HTTP redirect
|
|
158
|
+
interception has been proved in dev and is pending deployment; confirm
|
|
159
|
+
support in the target environment before relying on it.
|
|
160
|
+
|
|
161
|
+
The default Google profile is Test User (testuser@example.com). Repeated
|
|
162
|
+
Google sign-ins in one copy use that identity; the Login node's persona
|
|
163
|
+
does not select the Google user. Copies still isolate app sessions/data.
|
|
164
|
+
The public definition has no per-persona Google identity setting today.
|
|
165
|
+
|
|
166
|
+
If Google is reached through hosted Auth0, Clerk or another intermediary,
|
|
167
|
+
this Google emulator does not emulate that intermediary. Inspect where
|
|
168
|
+
token exchange occurs. Prove the customer's actual library and trust setup;
|
|
169
|
+
do not disable token verification or alter production auth to pass a test.
|
|
170
|
+
Non-Node clients must trust /etc/emulation/ca.crt through their runtime's
|
|
171
|
+
supported mechanism. This native Login-node contract does not inject
|
|
172
|
+
authentication into an external Playwright suite.
|
|
173
|
+
|
|
174
|
+
REFERENCES
|
|
175
|
+
|
|
176
|
+
https://docs.runcofactor.com/reference/sandbox-definition#authentication
|
|
177
|
+
https://docs.runcofactor.com/reference/sandbox-emulation#google-oauth-and-third-party-login
|
|
178
|
+
cofactor sandbox guide v2 personas
|
|
179
|
+
cofactor sandbox guide v2 emulation
|
|
180
|
+
cofactor sandbox guide v2 ports
|
|
181
|
+
`;
|
|
63
182
|
|
|
64
183
|
// ../content/src/sandbox-v2-guide/env.txt
|
|
65
184
|
var env_default = "An environment's `env` block (under `environments.<name>.env`) overrides\nenvironment variables for exactly three things:\n\n - running services that declare `reload_on_prepare: true` \u2014 they are\n restarted by `prepare` (and by an in-place move to a new commit), so\n they always see the current chain's merged env;\n - `lifecycle.prepare` commands, run fresh on every prepare;\n - actions, which run with the prepare env plus that call's\n `INPUT_<NAME>` values.\n\n environments:\n beta-features:\n extends: default\n description: Same as default, with the timeline view turned on.\n env:\n FEATURE_FLAGS: timeline\n\nIt does NOT reach:\n\n - `lifecycle.build` steps. Build steps run once into the repo-base layer,\n before any environment is chosen, so an environment's `env` does not\n exist at build time. A value your build bakes into its output (e.g. a\n front-end bundle's API URL or feature flags) can't vary by\n environment: read it at serve time instead, or set it with a seed\n action.\n - services that don't declare `reload_on_prepare`. A service without it\n only ever sees the definition's top-level `env` and layer-time param\n values (from when its layer was built) \u2014 it is not restarted when a\n sandbox is prepared, so a per-environment override would never take\n effect for it.\n\nAn environment's `env` values are used as written: `${params.name}` is\nsubstituted only in the definition's top-level `env`.\n\n`env` MERGE ORDER\n\n Composition follows the same parent-then-child rule as seeds (see\n `environments`): the resolved chain's env is the parent's `env` merged\n first, then the child's own, so a child environment can override a\n specific key its parent sets without having to repeat the rest.\n\nRELATION TO THE DEFINITION'S TOP-LEVEL `env`\n\n The definition's top-level `env` (outside any environment) is the base:\n fixed values and `${params.name}` substitutions that apply regardless of\n which environment is claimed \u2014 `DATABASE_URL`, `PUBLIC_URL` and so on in\n the example app. An environment's `env` is layered on top of that base\n for the reachable surfaces above, not a replacement for it.\n";
|
|
66
185
|
|
|
67
186
|
// ../content/src/sandbox-v2-guide/emulation.txt
|
|
68
|
-
var emulation_default = "Emulation makes third-party services deterministic and inspectable: instead\nof a sandbox's outbound calls to an email provider, Google, Jira or Slack\nleaving the VM, they're intercepted by an in-sandbox emulator that behaves\nlike the real thing well enough for tests, and lets a test look at what\nwas sent.\n\nPLUGINS\n\n Declaring a plugin id under `emulation:` turns on its interception and\n adds it to what `envs` reports.\n emulation:\n email: {}\n\n Available plugin ids:\n email Intercepts the HTTP send APIs of several providers\n (SES, SendGrid, Resend, Postmark, Mailgun) so outbound\n mail is captured instead of actually sent. SES is\n intercepted in us-east-1 unless you list others:\n `email: { sesRegions: [eu-west-1] }`; add other hosts\n with `extraDomains`. Mail through a host that isn't\n intercepted leaves the sandbox for real.\n google-oauth Intercepts Google's OAuth endpoints, for testing login\n flows without a real Google account.\n jira Intercepts the Jira Cloud REST API of your site:\n `jira: { tenant: acme }` serves acme.atlassian.net.\n `tenant` is required (validate doesn't check it). It has\n no operations.\n slack Intercepts Slack's API for posted-message flows.\n\n `cofactor sandbox envs` lists each declared plugin's operations.\n\nHOW INTERCEPTION WORKS\n\n Inside the sandbox, the domains a plugin owns (e.g.\n `email.us-east-1.amazonaws.com` for the email plugin's SES routes) are\n resolved via `/etc/hosts` to the emulator instead of the real internet.\n The emulator serves those domains over real TLS, using a CA generated\n when the version's ready snapshot is built (every copy shares it). It\n survives an emulator restart, so services that read\n `NODE_EXTRA_CA_CERTS` once at startup don't break when the emulator\n restarts. Services with `reload_on_prepare: true`, prepare commands and\n actions get `NODE_EXTRA_CA_CERTS=/etc/emulation/ca.crt` so they trust\n that CA. A service WITHOUT `reload_on_prepare` never gets it (nor any\n emulation env): its calls to an emulated domain fail TLS. Any service that\n talks to an emulated provider must be `reload_on_prepare`.\n\n NO PASSTHROUGH. An unmatched path on an emulated domain doesn't\n fall through to the real provider \u2014 it returns 501 and the attempt is\n recorded, so a route the app calls that no plugin has implemented yet\n fails loudly and visibly instead of silently reaching a real third\n party.\n\n The emulator depends on `openssl` for certificate generation. For a\n GitHub-delivered app, the platform installs it before your `setup`\n runs, so a definition doesn't need to install it itself; a pushed image\n that declares `emulation` must include it (`apt-get install -y\n openssl`), or the emulator never turns healthy and prepare fails.\n\nOPERATIONS\n\n Each plugin declares typed operations \u2014 the same shape as an action\n (id, description, typed input, typed output) \u2014 that appear right alongside\n your definition's own actions, named `<plugin>.<op>`. They run exactly\n like an action: `cofactor sandbox run <id> <plugin.op>`, or as a seed\n step (`action: email.sent-emails`). Cofactor calls the emulator's control\n plane directly rather than shelling into the sandbox for these.\n\n Today: `email.sent-emails` \u2014 no input, returns every email captured since\n boot or the last reset. In the example app, it verifies that\n `invite-user` actually sent the invitation:\n cofactor sandbox run <id> invite-user \\\n --input workspace_id=<acme.workspace_id> \\\n --input email=new@example.test --input role=member --input as=owner\n cofactor sandbox run <id> email.sent-emails\n\n\nAUTHENTICATION AND PROVIDER LOGIN\n\n For normal persona-based flows, have `authentication.run` create the\n application's session; see `cofactor sandbox guide v2 authentication`.\n For a flow testing Google login, start without that pre-authenticated state\n and use the `google-oauth` emulator through the app's actual login button.\n This avoids treating a successful pre-authenticated landing as proof of the\n provider redirect and callback.\n\n For Google callback configuration, app-code compatibility and the default\n single fixture identity, read `cofactor sandbox guide v2 authentication`.\n Google browser authorization and the backend token/user-info handshake both\n need to work. App HTTP redirect interception is pending rollout; verify\n the full signed-out journey in the target environment. Hosted identity\n intermediaries are not covered by declaring google-oauth alone.\n\n Reference:\n https://docs.runcofactor.com/reference/sandbox-emulation#google-oauth-and-third-party-login\n";
|
|
187
|
+
var emulation_default = "Emulation makes third-party services deterministic and inspectable: instead\nof a sandbox's outbound calls to an email provider, Google, Jira or Slack\nleaving the VM, they're intercepted by an in-sandbox emulator that behaves\nlike the real thing well enough for tests, and lets a test look at what\nwas sent.\n\nPLUGINS\n\n Declaring a plugin id under `emulation:` turns on its interception and\n adds it to what `envs` reports.\n emulation:\n email: {}\n\n Available plugin ids:\n email Intercepts the HTTP send APIs of several providers\n (SES, SendGrid, Resend, Postmark, Mailgun) so outbound\n mail is captured instead of actually sent. SES is\n intercepted in us-east-1 unless you list others:\n `email: { sesRegions: [eu-west-1] }`; add other hosts\n with `extraDomains`. Mail through a host that isn't\n intercepted leaves the sandbox for real.\n SMTP (nodemailer, Django's SMTP backend, Action\n Mailer\u2026) is not intercepted. Leave SMTP unconfigured\n in the sandbox so the app sends nothing, or ship an\n SMTP catcher such as Mailpit in the image, run it as\n one of your `services`, and point the app's SMTP host\n at 127.0.0.1.\n google-oauth Intercepts Google's OAuth endpoints, for testing login\n flows without a real Google account.\n jira Intercepts the Jira Cloud REST API of your site:\n `jira: { tenant: acme }` serves acme.atlassian.net.\n `tenant` is required (validate doesn't check it). It has\n no operations.\n slack Intercepts Slack's API for posted-message flows.\n\n `cofactor sandbox envs` lists each declared plugin's operations.\n\nHOW INTERCEPTION WORKS\n\n Inside the sandbox, the domains a plugin owns (e.g.\n `email.us-east-1.amazonaws.com` for the email plugin's SES routes) are\n resolved via `/etc/hosts` to the emulator instead of the real internet.\n The emulator serves those domains over real TLS, using a CA generated\n when the version's ready snapshot is built (every copy shares it). It\n survives an emulator restart, so services that read\n `NODE_EXTRA_CA_CERTS` once at startup don't break when the emulator\n restarts. Services with `reload_on_prepare: true`, prepare commands and\n actions get `NODE_EXTRA_CA_CERTS=/etc/emulation/ca.crt` so they trust\n that CA. A service WITHOUT `reload_on_prepare` never gets it (nor any\n emulation env): its calls to an emulated domain fail TLS. Any service that\n talks to an emulated provider must be `reload_on_prepare`.\n\n NO PASSTHROUGH. An unmatched path on an emulated domain doesn't\n fall through to the real provider \u2014 it returns 501 and the attempt is\n recorded, so a route the app calls that no plugin has implemented yet\n fails loudly and visibly instead of silently reaching a real third\n party.\n\n The emulator depends on `openssl` for certificate generation. For a\n GitHub-delivered app, the platform installs it before your `setup`\n runs, so a definition doesn't need to install it itself; a pushed image\n that declares `emulation` must include it (`apt-get install -y\n openssl`), or the emulator never turns healthy and prepare fails.\n\nOPERATIONS\n\n Each plugin declares typed operations \u2014 the same shape as an action\n (id, description, typed input, typed output) \u2014 that appear right alongside\n your definition's own actions, named `<plugin>.<op>`. They run exactly\n like an action: `cofactor sandbox run <id> <plugin.op>`, or as a seed\n step (`action: email.sent-emails`). Cofactor calls the emulator's control\n plane directly rather than shelling into the sandbox for these.\n\n Today: `email.sent-emails` \u2014 no input, returns every email captured since\n boot or the last reset. In the example app, it verifies that\n `invite-user` actually sent the invitation:\n cofactor sandbox run <id> invite-user \\\n --input workspace_id=<acme.workspace_id> \\\n --input email=new@example.test --input role=member --input as=owner\n cofactor sandbox run <id> email.sent-emails\n\n\nAUTHENTICATION AND PROVIDER LOGIN\n\n For normal persona-based flows, have `authentication.run` create the\n application's session; see `cofactor sandbox guide v2 authentication`.\n For a flow testing Google login, start without that pre-authenticated state\n and use the `google-oauth` emulator through the app's actual login button.\n This avoids treating a successful pre-authenticated landing as proof of the\n provider redirect and callback.\n\n For Google callback configuration, app-code compatibility and the default\n single fixture identity, read `cofactor sandbox guide v2 authentication`.\n Google browser authorization and the backend token/user-info handshake both\n need to work. App HTTP redirect interception is pending rollout; verify\n the full signed-out journey in the target environment. Hosted identity\n intermediaries are not covered by declaring google-oauth alone.\n\n Reference:\n https://docs.runcofactor.com/reference/sandbox-emulation#google-oauth-and-third-party-login\n";
|
|
69
188
|
|
|
70
189
|
// ../content/src/sandbox-v2-guide/object-storage.txt
|
|
71
190
|
var object_storage_default = "An app that stores files (uploads, attachments, exports) can have an\nemulated S3 or Google Cloud Storage server in every sandbox, instead of\nreaching a real bucket. You declare the provider and the buckets; the\nsandbox runs the server, creates the buckets, and gives your app the\nstandard client settings for that provider.\n\nDECLARING IT\n\n object_storage:\n provider: s3 # or gcs\n buckets: [attachments, exports]\n\n - One provider per app. Bucket names are 3\u201363 lowercase letters,\n digits, `-` and `.`, starting and ending with a letter or digit, with\n no `..` and not shaped like an IP address. A GCS bucket can't be\n named `storage`, `upload`, `download` or `batch`.\n - The buckets exist before your first prepare command runs.\n - Declaring it adds an implicit public port named `object-storage` (you\n don't declare it yourself, and you can't name a port of your own\n `object-storage`). It counts toward the 8-port limit.\n - The server listens on port 9000 (S3), or 4443 and 4444 (GCS);\n a port of your own can't use those numbers, and a service of your own\n can't be named `object-storage` or `object-storage-front`.\n\n For a pushed image, `cofactor sandbox push` adds the declared provider's\n server to the image; you don't install anything.\n\nYOUR DATA IN EACH SANDBOX\n\n Each sandbox gets its own copy of the buckets. Files your seeds put there\n (through an action, below) are captured in the ready snapshot with the\n rest of the sandbox, so every claim starts with them. A test that\n uploads a file changes only its own sandbox, and the data is gone when\n the sandbox is destroyed.\n\nTHE ENV YOUR APP GETS\n\n Every service, prepare command and action runs with the provider's\n standard client variables:\n\n s3 AWS_ENDPOINT_URL_S3 http://127.0.0.1:9000\n AWS_ACCESS_KEY_ID cofactor-sandbox\n AWS_SECRET_ACCESS_KEY cofactor-sandbox-secret\n AWS_REGION us-east-1\n\n gcs STORAGE_EMULATOR_HOST http://127.0.0.1:4443\n GOOGLE_CLOUD_PROJECT sandbox\n\n The credentials are fixed and the same in every sandbox. If your app\n reads different names (say `ATTACHMENTS_ENDPOINT`), set them in your\n definition's `env:`, which also overrides any of the above (an\n environment's `env:` too; your image's own ENV does not):\n env:\n ATTACHMENTS_ENDPOINT: http://127.0.0.1:9000\n ATTACHMENTS_BUCKET: attachments\n\nS3: CLIENT SETTINGS\n\n AWS SDK v3 (JavaScript) and boto3 1.28 or later read\n `AWS_ENDPOINT_URL_S3` themselves. Your app must use PATH-STYLE\n addressing (`http://host/bucket/key`, not `http://bucket.host/key`):\n\n // AWS SDK v3\n new S3Client({ forcePathStyle: true })\n\n # boto3\n boto3.client(\"s3\", config=Config(s3={\"addressing_style\": \"path\"}))\n\n Older SDKs that don't read `AWS_ENDPOINT_URL_S3`: pass it as the\n client's endpoint yourself.\n\nGCS: CLIENT SETTINGS\n\n Point your client at `STORAGE_EMULATOR_HOST`. Google's Node\n (`@google-cloud/storage`) and Python (`google-cloud-storage`) clients\n read it themselves and skip authentication when it is set, so `new\n Storage()` and `storage.Client()` work with no credentials. On an older\n Python client that doesn't, pass both yourself:\n storage.Client(\n credentials=AnonymousCredentials(),\n client_options={\"api_endpoint\": os.environ[\"STORAGE_EMULATOR_HOST\"]},\n )\n For another language, set your client's endpoint to\n `STORAGE_EMULATOR_HOST` and give it no (anonymous) credentials.\n\nLINKS A BROWSER OPENS: THE PUBLIC URL\n\n `STORAGE_EMULATOR_HOST` and `AWS_ENDPOINT_URL_S3` are only reachable\n inside the sandbox. A presigned (S3) or signed (GCS) URL your app hands\n to a browser must use the `object-storage` port's public URL instead.\n Its origin is a `{port}` param, which you pass to your app:\n params:\n storage_url: { port: object-storage }\n env:\n STORAGE_PUBLIC_URL: ${params.storage_url}\n\n The service that reads it needs `reload_on_prepare: true`, like any\n service that reads a param (see the `upload` topic).\n\n Anyone with that URL reaches the storage: S3 with the fixed credentials\n above, GCS with no authentication at all. It holds the sandbox's test\n data, like every other port of the sandbox, so never put real data in\n it.\n\n S3: sign the URL against the public origin with a second client used\n only for presigning (signing makes no network call), and keep every\n real read and write on the default one:\n const presigner = new S3Client({\n endpoint: process.env.STORAGE_PUBLIC_URL,\n forcePathStyle: true,\n });\n const url = await getSignedUrl(presigner,\n new GetObjectCommand({ Bucket: 'attachments', Key: key }));\n The name is yours: an app that already has such a setting (e.g.\n `S3_PRESIGN_PUBLIC_ENDPOINT: ${params.storage_url}`) just sets it. The\n public URL preserves the request's Host, so the signature holds, and\n browser uploads through a presigned PUT are allowed from any origin.\n\n GCS: generate signed URLs with the public origin as their host (Node:\n the `host` option of `file.getSignedUrl`; Python: the\n `api_access_endpoint` argument of `blob.generate_signed_url`). Signing\n still needs a credential that can sign, such as a service account key\n file; the emulator doesn't check signatures, so give the sandbox a\n throwaway key, never a real one. Signed downloads and signed uploads\n work. A browser must upload in ONE signed PUT: a resumable upload\n started from a browser is answered with an address inside the sandbox\n (127.0.0.1) that the browser can't reach, so resumable uploads work\n only from inside the sandbox (your app's server, an action).\n\nSEEDING FILES WITH AN ACTION\n\n An action can put files in a bucket, so a seed can start every claim\n with them. If your app records each file in its database (an attachment\n row pointing at the object), go through the app's own upload API\n instead, so the row exists too \u2014 the same rule as any other seed (see\n `seeds-and-actions`). For a file the app only reads (a logo, an import\n template), write it straight to the bucket. The examples need only\n curl (7.75 or later, for `--aws-sigv4`):\n\n #!/usr/bin/env bash\n # sandbox/actions/add-template.sh (S3)\n set -euo pipefail\n curl -fsS --aws-sigv4 \"aws:amz:$AWS_REGION:s3\" \\\n --user \"$AWS_ACCESS_KEY_ID:$AWS_SECRET_ACCESS_KEY\" \\\n -H 'content-type: text/csv' -T sandbox/files/import-template.csv \\\n \"$AWS_ENDPOINT_URL_S3/exports/templates/import-template.csv\" >&2\n echo '{\"key\": \"templates/import-template.csv\"}'\n\n #!/usr/bin/env bash\n # sandbox/actions/add-template.sh (GCS)\n set -euo pipefail\n query='uploadType=media&name=templates/import-template.csv'\n curl -fsS -X POST -H 'content-type: text/csv' \\\n --data-binary @sandbox/files/import-template.csv \\\n \"$STORAGE_EMULATOR_HOST/upload/storage/v1/b/exports/o?$query\" >&2\n echo '{\"key\": \"templates/import-template.csv\"}'\n\nLIMITS\n\n - One provider per app.\n - GCS: one upload request carries at most 128 MB, a signed PUT from a\n browser included.\n - GCS: a browser uploads only through a single signed PUT (above).\n - The data lives only in the sandbox: nothing is copied from or to a\n real bucket, and it goes when the sandbox is destroyed. It is\n reachable on the public `object-storage` URL: test data only.\n";
|
|
@@ -80,7 +199,7 @@ var verify_default = "Two different checks, at two different costs.\n\n`cofactor
|
|
|
80
199
|
var cli_default = "All commands are under `cofactor sandbox`, and every sandbox belongs to an\napplication (`--application <name|id>`). `--env` is the CLI's *global*\nflag for which Cofactor stage to talk to (`dev` / `prod`) \u2014 it has nothing\nto do with a sandbox's environment, which is always `--environment`.\n\ncofactor sandbox validate [file] [--upload]\n Offline schema + cross-check of a `cofactor.sandbox.yaml`. Defaults to\n the definition in the current directory if `file` is omitted. `--upload`\n checks a definition for pushed images (no `setup`, no `lifecycle.build`);\n without it, one built from a GitHub checkout. See the `verify` topic for\n the issue format.\n\ncofactor sandbox connect --application A --repo owner/name [--dir D]\n Builds the application's sandboxes from a GitHub repository: the\n `cofactor.sandbox.yaml` under `--dir` (default: the repo root) at each\n commit. Its environments become the application's sandbox environments\n and its personas the application's personas.\n\ncofactor sandbox push --application A <image> [--definition F] [--tag T]... [--wait]\n For an application that gives Cofactor no repository access: pushes a\n built image (with its `cofactor.sandbox.yaml`) as the application's next\n version. See the `upload` topic.\n\ncofactor sandbox envs --application A [--json]\n The application's delivery (GitHub or pushed images), its current\n version, recent pushes, sandbox environments, and what its definition\n offers: every environment (descriptions, seeds), ports, personas,\n actions and emulator operations (with their schemas). This is what an\n agent reads to decide which environment to claim and how to call each\n action once it has a sandbox.\n\ncofactor sandbox claim --application A [--environment E] [--at V] [--wait]\n Claims a sandbox: a fresh copy of the ready snapshot for the version and\n environment (built first \u2014 build, `prepare`, seeds \u2014 if it doesn't exist\n yet), given its own URLs and logins by `prepare`. A version is a commit\n SHA for a GitHub-delivered application, or a push id or tag for a pushed\n one. Without `--at`: for GitHub, the default branch's current ready\n snapshot of the environment (the periodic refresh keeps it within a few\n hours of the head while the environment is in use), else the head; for\n pushes, the newest push. `--environment` defaults to `default`. `--wait`\n blocks until the sandbox is ready (or failed) instead of returning\n immediately with a claim id you'd otherwise have to poll with `show`.\n\ncofactor sandbox show <id>\n Current status, per-port URLs, persona logins (decrypted \u2014 this is where\n they're meant to be read from, never a PR comment), seed outputs, and\n the available actions and operations.\n\ncofactor sandbox run <id> <action|plugin.op> [--input k=v ...] [--json-input '{...}']\n Runs a definition action or an emulator operation (named `<plugin>.<op>`,\n e.g. `email.sent-emails`) against a claimed sandbox, waits for it, and\n prints its output. `--input` is repeatable and coerced to the action/\n operation's declared type (string/number/integer/boolean/persona);\n `--json-input` passes the whole input as JSON, overriding `--input`.\n Invalid input prints the action or operation's input schema instead of\n running it, so you can see what's expected.\n\ncofactor sandbox destroy <id>\n Tears the sandbox down. Always do this when a claimed sandbox is no\n longer needed \u2014 it isn't reclaimed automatically mid-session.\n\ncofactor sandbox verify-env --application A [--environment E]\n Claim \u2192 wait \u2192 report seed outputs and timings \u2192 destroy, for each\n environment of the current version (or the one named). The end-to-end\n proof that an environment's seed chain actually works, not just that it's\n well-formed. See the `verify` topic.\n\ncofactor sandbox preview --application A --pr N\n A pull request's preview of a GitHub-delivered application: status, URLs\n and the logins its PR comment deliberately leaves out.\n\ncofactor sandbox guide v2 [topic]\n Prints this guide. Omit `topic` to print every section; name one (e.g.\n `upload`) to print just that topic.\n\nTHE CORE LOOP\n\n validate \u2192 (connect | push) \u2192 envs \u2192 claim --wait \u2192 run (actions/\n operations, as many as the test needs) \u2192 destroy. Always validate a\n changed definition before pushing it \u2014 see the `verify` topic for why.\n";
|
|
81
200
|
|
|
82
201
|
// ../content/src/sandbox-v2-guide/upload.txt
|
|
83
|
-
var upload_default = 'PUSHED IMAGES: SANDBOXES WITHOUT REPOSITORY ACCESS\n\nAn application whose team gives Cofactor no access to its repository\ndelivers its sandboxes as built images: build your app\'s image as usual,\nthen push it with its `cofactor.sandbox.yaml` whenever you have a new\nbuild (nightly, per release, per merge \u2014 your cadence, your CI or your\nlaptop). Each push is a version of the application; claims use the newest\none unless they name another with `--at <push id or tag>`. A tag names the\nnewest ready push that carries it: pushing `--tag nightly` again moves the\ntag to the new push, and older pushes stay claimable by their id.\n\n docker buildx build --platform linux/amd64 -t tracker:sandbox --load .\n cofactor sandbox validate --upload cofactor.sandbox.yaml\n cofactor sandbox push --application "Project Tracker" tracker:sandbox \\\n --definition cofactor.sandbox.yaml --tag 2026-10-01 --wait\n cofactor sandbox envs --application "Project Tracker"\n cofactor sandbox claim --application "Project Tracker" --environment default --wait\n\n`push` adds Cofactor\'s layers to your image (its sandbox helper as the\nentrypoint, plus the object storage server when the definition declares\n`object_storage`) and a small layer with your definition, reads the image\nout of your local Docker (`docker save`), and uploads only the layers\nCofactor doesn\'t already have for your organization (see FAST RE-PUSHES).\nCofactor boots it once until every service is healthy (`--wait` waits for\nthat). Then each environment in the definition\nbecomes a sandbox environment of the application, and each persona one of\nits personas. The first claim of an environment builds its ready snapshot\n(prepare + seeds, about a minute); later claims copy it in seconds.\n\nWHAT THE IMAGE MUST BE\n\n - linux/amd64. On an Apple Silicon Mac, build with\n `--platform linux/amd64`; `push` refuses any other platform.\n - glibc-based with bash (Debian, Ubuntu, `python:*-slim`, `node:*`\n non-alpine\u2026). Alpine/musl and distroless images do not work: the\n helper is a glibc binary and every command runs through `bash -lc`.\n - A WORKDIR other than `/`. Services, prepare commands and actions run\n from it, and the helper gives it to the sandbox user (below).\n - Built already. There is no build step in a pushed sandbox: everything\n the app needs (dependencies, compiled assets, a database server if it\n uses one) is in the image.\n\nFAST RE-PUSHES\n\n A push uploads only the layers that changed since an earlier push (in\n the last 30 days); `push` says how much it sent and how much Cofactor\n already had. Two things keep a re-push small:\n\n - Order the Dockerfile so what changes most comes last: system packages,\n then the dependency manifest and lockfile and the install step, then\n your source. A code change then rebuilds, and uploads, only the last\n layers:\n FROM python:3.12-slim\n RUN apt-get update && apt-get install -y --no-install-recommends curl jq\n WORKDIR /app\n COPY requirements.txt .\n RUN pip install -r requirements.txt\n COPY . .\n - In CI, keep the build cache between runs. A runner that starts with an\n empty cache rebuilds every step, and every rebuilt layer is new to\n Cofactor even when nothing in it changed. With GitHub Actions:\n docker buildx build --platform linux/amd64 --load -t app:sandbox \\\n --cache-from type=gha --cache-to type=gha,mode=max .\n (or `type=registry` with a cache image of your own).\n\n CI authenticates with an API key (`COFACTOR_API_TOKEN`). Give it only\n what the job does: `sandbox:read`, `sandbox:publish` and `property:read`\n to push; add `sandbox:create` and `sandbox:delete` to claim and destroy,\n and `sandbox:run` to run actions.\n\n For pull requests, push each with its own tag and claim it by that tag:\n cofactor sandbox push --application "Project Tracker" app:sandbox --tag pr-123 --wait\n cofactor sandbox claim --application "Project Tracker" --environment default --at pr-123 --wait\n A pull request\'s push shares every layer below the source with main\'s.\n\nHOW YOUR IMAGE RUNS IN A SANDBOX\n\n - Its ENTRYPOINT and CMD are NOT used. Start each process yourself with a\n `services.<name>.run` command (often your entrypoint script).\n - Its ENV and WORKDIR ARE applied, as `docker run` would.\n - Everything \u2014 services, prepare commands, actions \u2014 runs as an\n unprivileged user with uid 1000 (the sandbox adds that account if your\n image has none). The WORKDIR tree is made theirs; anything the app\n writes must be under the WORKDIR or `/tmp`, not elsewhere in a root-\n owned path.\n - One machine, no Docker inside: a database is a process you run as a\n service (Postgres installed in the image, or SQLite), not a container.\n For S3 or GCS, declare `object_storage` and the sandbox runs the\n server for you (see the `object-storage` topic).\n - Each public port gets its own https URL, unique per sandbox. Its\n origin is a param \u2014 `params: { web_url: { port: web } }` \u2014 that you\n pass to the app through `env` (e.g. `PUBLIC_URL: ${params.web_url}`).\n\nTHE DEFINITION FOR PUSHED IMAGES\n\n The same `cofactor.sandbox.yaml` as for a repository (see the\n `definition` topic), minus everything about building: no `setup`, no\n `lifecycle.build`, no `after_build`. A minimal one for the example app,\n built as one image that serves its UI and API on port 8080:\n\n version: 1\n resources: { vcpus: 2, memory_mib: 4096 }\n ports:\n web: { port: 8080, access: public }\n params:\n web_url: { port: web }\n env:\n PUBLIC_URL: ${params.web_url}\n services:\n app:\n run: exec ./start.sh\n health: { http: 8080, path: /health }\n reload_on_prepare: true\n personas:\n owner: { email: owner@example.test }\n lifecycle:\n prepare:\n - bash sandbox/personas.sh\n actions:\n create-workspace:\n description: A new workspace, created by the given persona, who becomes its owner.\n run: sandbox/actions/create-workspace.sh\n input:\n name: { type: string, required: true }\n as: { type: persona, required: true }\n output:\n workspace_id: { type: string }\n environments:\n default:\n description: Workspace "Acme", owned by the owner persona.\n seeds:\n - id: acme\n action: create-workspace\n input: { name: Acme, as: owner }\n\n From here, add what your tests need, in the order "WHAT TO BUILD FIRST"\n in the `seeds-and-actions` topic gives: a persona per role, an\n environment per plan or flag combination, and actions for the core\n objects.\n\n Keep the file anywhere in your repo: `validate --upload <file>` and\n `push --definition <file>` read it from where you point them, and the\n push puts it in the image. Paths inside it (`run:` of actions, prepare\n commands) are relative to the WORKDIR, so ship your sandbox scripts in\n the image (e.g. `COPY sandbox/ sandbox/`).\n\n Services start when the image is first booted, BEFORE any sandbox exists,\n with params at placeholder values. A service that needs the sandbox\'s own\n values (its URL, a `{secret: true}` param) sets `reload_on_prepare: true`:\n it is started (and restarted) by each sandbox\'s prepare instead. In\n doubt, set it on your app\'s service.\n\n `health` passes when the http path answers below 400 (or the command\n exits 0) within 5 minutes of the service starting.\n\nPERSONAS: MAKING THE LOGINS WORK\n\n Cofactor generates a fresh password for every persona in every sandbox.\n Your `lifecycle.prepare` commands receive them as `COFACTOR_PERSONAS`, a\n JSON map `{ "<persona>": { "email": "...", "password": "..." } }`, and\n must make each login work in your app: create or update that user with\n that password (through your app\'s API, its CLI, or its database). A\n persona whose login doesn\'t work breaks every test that signs in as it.\n A persona\'s `email` is yours to choose: pick an account your app already\n has (its seed data) and set its password, or have prepare create it.\n Cofactor\'s test runs then sign in with these logins automatically, as\n the application\'s persona of the same name.\n\n Prepare runs while your app is up: the `reload_on_prepare` services are\n running and healthy (restarted first when their environment changed),\n the others have run since the image was booted. So write the logins\n somewhere the running app reads on every sign-in (its database, or its\n own API). If your app loads its accounts once at startup (a file or\n config read into memory), change it to read them per request, or the new\n passwords won\'t take. For an app whose logins are fixtures, a common fix\n is a small JSON file the prepare script writes from `COFACTOR_PERSONAS`\n and the app checks on each sign-in before its fixtures.\n\nACTIONS AND SEEDS\n\n An action is a script in the image with typed input and output (the\n `seeds-and-actions` topic covers designing them). Inputs arrive as\n `INPUT_<NAME>` env vars (a `persona` input carries the persona\'s name,\n e.g. `owner`), alongside the definition\'s `env:`, every param as\n `COFACTOR_PARAM_<NAME>`, and `COFACTOR_PERSONAS`. The script prints its\n result as a JSON object on the LAST stdout line that starts with `{`\n (send everything else to stderr). It runs inside the sandbox, so it\n reaches your app on localhost. An environment\'s `seeds` call actions in\n order, once per version, before the snapshot every claim copies;\n `${<step id>.<output>}` passes one step\'s output to a later step. Run\n one against a live sandbox with `cofactor sandbox run <id> <action>\n --input name=... --input as=owner`.\n\n An action that signs in as a persona and creates data through the app\'s\n own API (here a cookie-session JSON API on port 8080; the port, paths and\n JSON fields are the example app\'s, use your app\'s). It needs jq and\n curl, which slim base images lack: add them to your Dockerfile\n (`apt-get install -y --no-install-recommends jq curl`).\n\n #!/usr/bin/env bash\n # sandbox/actions/create-workspace.sh\n set -euo pipefail\n login=$(jq -c --arg p "$INPUT_AS" \'.[$p] | {email, password}\' <<<"$COFACTOR_PERSONAS")\n jar=$(mktemp)\n curl -fsS -c "$jar" -H \'content-type: application/json\' \\\n -d "$login" http://127.0.0.1:8080/api/auth/login >&2\n id=$(curl -fsS -b "$jar" -H \'content-type: application/json\' \\\n -d "$(jq -nc --arg n "$INPUT_NAME" \'{name: $n}\')" \\\n http://127.0.0.1:8080/api/workspaces | jq -er \'.id\')\n jq -nc --arg id "$id" \'{workspace_id: $id}\'\n\n\nTROUBLESHOOTING\n\n - `push` says the image is not linux/amd64, or its WORKDIR can\'t be\n used: rebuild as it says.\n - The push fails with "Cofactor could not start this image": a service\n exited or never turned healthy. The message ends with that service\'s\n log. To reproduce, open a shell in the image \u2014\n docker run --rm -it --entrypoint bash <image>\n \u2014 and run that service\'s `run` command from the WORKDIR. In the\n sandbox it runs as uid 1000 with the WORKDIR made theirs; plain\n `docker run --user 1000` leaves the WORKDIR root-owned, so a write\n failure there is not the sandbox\'s.\n - A claim fails in prepare or a seed: the error carries the command\'s\n output. Fix the script, rebuild the image and push again.\n - To try prepare and actions before pushing, run the app as usual and\n call your scripts in it with the variables they read (a sandbox also\n sets the definition\'s `env:` and runs them as uid 1000):\n docker run -d --name try -p 8080:8080 <image>\n docker exec -e COFACTOR_PERSONAS=\'{"owner":{"email":"owner@example.test","password":"pw-1"}}\' try bash sandbox/personas.sh\n docker exec -e COFACTOR_PERSONAS=\'\u2026\' -e INPUT_NAME=Acme -e INPUT_AS=owner try bash sandbox/actions/create-workspace.sh\n (`docker exec` starts in the image\'s WORKDIR.)\n - `cofactor sandbox logs` and `diagnostics` are for the older sandboxes;\n for these, `cofactor sandbox show <id>` has the status and any error.\n - "push the image again": Cofactor\'s sandbox helper changed since that\n push; a fresh `cofactor sandbox push` picks up the current one.\n - A layer larger than 5 GB is refused; split or slim it.\n - Every push uploads the whole image: the build cache is missing (see\n FAST RE-PUSHES), or the Dockerfile copies the source before\n installing dependencies.\n';
|
|
202
|
+
var upload_default = 'PUSHED IMAGES: SANDBOXES WITHOUT REPOSITORY ACCESS\n\nAn application whose team gives Cofactor no access to its repository\ndelivers its sandboxes as built images: build your app\'s image as usual,\nthen push it with its `cofactor.sandbox.yaml` whenever you have a new\nbuild (nightly, per release, per merge \u2014 your cadence, your CI or your\nlaptop). Each push is a version of the application; claims use the newest\none unless they name another with `--at <push id or tag>`. A tag names the\nnewest ready push that carries it: pushing `--tag nightly` again moves the\ntag to the new push, and older pushes stay claimable by their id.\n\n docker buildx build --platform linux/amd64 -t tracker:sandbox --load .\n cofactor sandbox validate --upload cofactor.sandbox.yaml\n cofactor sandbox push --application "Project Tracker" tracker:sandbox \\\n --definition cofactor.sandbox.yaml --tag 2026-10-01 --wait\n cofactor sandbox envs --application "Project Tracker"\n cofactor sandbox claim --application "Project Tracker" --environment default --wait\n\n`push` adds Cofactor\'s layers to your image (its sandbox helper as the\nentrypoint, plus the object storage server when the definition declares\n`object_storage`) and a small layer with your definition, reads the image\nout of your local Docker (`docker save`), and uploads only the layers\nCofactor doesn\'t already have for your organization (see FAST RE-PUSHES).\nCofactor boots it once until every service is healthy (`--wait` waits for\nthat). Then each environment in the definition\nbecomes a sandbox environment of the application, and each persona one of\nits personas. The first claim of an environment builds its ready snapshot\n(prepare + seeds, about a minute); later claims copy it in seconds.\n\nWHAT THE IMAGE MUST BE\n\n - linux/amd64. On an Apple Silicon Mac, build with\n `--platform linux/amd64`; `push` refuses any other platform.\n - glibc-based with bash (Debian, Ubuntu, `python:*-slim`, `node:*`\n non-alpine\u2026). Alpine/musl and distroless images do not work: the\n helper is a glibc binary and every command runs through `bash -lc`.\n If only your final stage is Alpine, keep the build stages and change\n that one `FROM` to its Debian equivalent (`node:24-alpine` \u2192\n `node:24-bookworm-slim`, `python:3.12-alpine` \u2192 `python:3.12-slim`),\n with `apt-get install` in place of `apk add`. Native dependencies\n compiled on Alpine don\'t run on glibc: install them in a glibc stage\n rather than copying `node_modules` or wheels out of an Alpine one.\n - A WORKDIR other than `/`. Services, prepare commands and actions run\n from it, and the helper gives it to the sandbox user (below).\n - Built already. There is no build step in a pushed sandbox: everything\n the app needs (dependencies, compiled assets, a database server if it\n uses one) is in the image.\n\nFAST RE-PUSHES\n\n A push uploads only the layers that changed since an earlier push (in\n the last 30 days); `push` says how much it sent and how much Cofactor\n already had. Two things keep a re-push small:\n\n - Order the Dockerfile so what changes most comes last: system packages,\n then the dependency manifest and lockfile and the install step, then\n your source. A code change then rebuilds, and uploads, only the last\n layers:\n FROM python:3.12-slim\n RUN apt-get update && apt-get install -y --no-install-recommends curl jq\n WORKDIR /app\n COPY requirements.txt .\n RUN pip install -r requirements.txt\n COPY . .\n - In CI, keep the build cache between runs. A runner that starts with an\n empty cache rebuilds every step, and every rebuilt layer is new to\n Cofactor even when nothing in it changed. With GitHub Actions:\n docker buildx build --platform linux/amd64 --load -t app:sandbox \\\n --cache-from type=gha --cache-to type=gha,mode=max .\n (or `type=registry` with a cache image of your own).\n\n CI authenticates with an API key (`COFACTOR_API_TOKEN`). Give it only\n what the job does: `sandbox:read`, `sandbox:publish` and `property:read`\n to push; add `sandbox:create` and `sandbox:delete` to claim and destroy,\n and `sandbox:run` to run actions.\n\n For pull requests, push each with its own tag and claim it by that tag:\n cofactor sandbox push --application "Project Tracker" app:sandbox --tag pr-123 --wait\n cofactor sandbox claim --application "Project Tracker" --environment default --at pr-123 --wait\n A pull request\'s push shares every layer below the source with main\'s.\n\nHOW YOUR IMAGE RUNS IN A SANDBOX\n\n - Its ENTRYPOINT and CMD are NOT used. Start each process yourself with a\n `services.<name>.run` command (often your entrypoint script).\n - Its ENV and WORKDIR ARE applied, as `docker run` would. Except PATH:\n commands run through `bash -lc` as uid 1000, and Debian\'s\n `/etc/profile` resets a non-root user\'s PATH, so an `ENV PATH=...`\n line doesn\'t reach them. Call such tools by\n absolute path (Postgres: `/usr/lib/postgresql/<n>/bin/postgres`) or\n set PATH in the service\'s own script.\n - Leave the image\'s USER as root (or unset). The sandbox starts as root\n and drops to uid 1000 itself; an image that ended on another `USER`\n has failed to boot with a permission error on `/etc/cofactor`.\n - Everything \u2014 services, prepare commands, actions \u2014 runs as an\n unprivileged user with uid 1000 (the sandbox adds that account if your\n image has none). The WORKDIR tree is made theirs; anything the app\n writes must be under the WORKDIR or `/tmp`, not elsewhere in a root-\n owned path.\n - One machine, no Docker inside: a database is a process you run as a\n service (Postgres installed in the image, or SQLite), not a container.\n For S3 or GCS, declare `object_storage` and the sandbox runs the\n server for you (see the `object-storage` topic).\n - Each public port gets its own https URL, unique per sandbox. Its\n origin is a param \u2014 `params: { web_url: { port: web } }` \u2014 that you\n pass to the app through `env` (e.g. `PUBLIC_URL: ${params.web_url}`).\n\nTHE DEFINITION FOR PUSHED IMAGES\n\n The same `cofactor.sandbox.yaml` as for a repository (see the\n `definition` topic), minus everything about building: no `setup`, no\n `lifecycle.build`, no `after_build`. A minimal one for the example app,\n built as one image that serves its UI and API on port 8080:\n\n version: 1\n resources: { vcpus: 2, memory_mib: 4096 }\n ports:\n web: { port: 8080, access: public }\n params:\n web_url: { port: web }\n env:\n PUBLIC_URL: ${params.web_url}\n services:\n app:\n run: exec ./start.sh\n health: { http: 8080, path: /health }\n reload_on_prepare: true\n personas:\n owner: { email: owner@example.test }\n lifecycle:\n prepare:\n - bash sandbox/personas.sh\n actions:\n create-workspace:\n description: A new workspace, created by the given persona, who becomes its owner.\n run: sandbox/actions/create-workspace.sh\n input:\n name: { type: string, required: true }\n as: { type: persona, required: true }\n output:\n workspace_id: { type: string }\n environments:\n default:\n description: Workspace "Acme", owned by the owner persona.\n seeds:\n - id: acme\n action: create-workspace\n input: { name: Acme, as: owner }\n\n From here, add what your tests need, in the order "WHAT TO BUILD FIRST"\n in the `seeds-and-actions` topic gives: a persona per role, an\n environment per plan or flag combination, and actions for the core\n objects.\n\n Keep the file anywhere in your repo: `validate --upload <file>` and\n `push --definition <file>` read it from where you point them, and the\n push puts it in the image. Paths inside it (`run:` of actions, prepare\n commands) are relative to the WORKDIR, so ship your sandbox scripts in\n the image (e.g. `COPY sandbox/ sandbox/`).\n\n Services start when the image is first booted, BEFORE any sandbox exists,\n with params at placeholder values. A service that needs the sandbox\'s own\n values (its URL, a `{secret: true}` param) sets `reload_on_prepare: true`:\n it is started (and restarted) by each sandbox\'s prepare instead. In\n doubt, set it on your app\'s service.\n\n `health` passes when the http path answers below 400 (or the command\n exits 0) within 5 minutes of the service starting.\n\nPERSONAS: MAKING THE LOGINS WORK\n\n Cofactor generates a fresh password for every persona in every sandbox.\n Your `lifecycle.prepare` commands receive them as `COFACTOR_PERSONAS`, a\n JSON map `{ "<persona>": { "email": "...", "password": "..." } }`, and\n must make each login work in your app: create or update that user with\n that password (through your app\'s API, its CLI, or its database). A\n persona whose login doesn\'t work breaks every test that signs in as it.\n A persona\'s `email` is yours to choose: pick an account your app already\n has (its seed data) and set its password, or have prepare create it.\n Cofactor\'s test runs then sign in with these logins automatically, as\n the application\'s persona of the same name.\n\n Prepare runs while your app is up: the `reload_on_prepare` services are\n running and healthy (restarted first when their environment changed),\n the others have run since the image was booted. So write the logins\n somewhere the running app reads on every sign-in (its database, or its\n own API). If your app loads its accounts once at startup (a file or\n config read into memory), change it to read them per request, or the new\n passwords won\'t take. For an app whose logins are fixtures, a common fix\n is a small JSON file the prepare script writes from `COFACTOR_PERSONAS`\n and the app checks on each sign-in before its fixtures.\n\nACTIONS AND SEEDS\n\n An action is a script in the image with typed input and output (the\n `seeds-and-actions` topic covers designing them). Inputs arrive as\n `INPUT_<NAME>` env vars (a `persona` input carries the persona\'s name,\n e.g. `owner`), alongside the definition\'s `env:`, every param as\n `COFACTOR_PARAM_<NAME>`, and `COFACTOR_PERSONAS`. The script prints its\n result as a JSON object on the LAST stdout line that starts with `{`\n (send everything else to stderr). It runs inside the sandbox, so it\n reaches your app on localhost. An environment\'s `seeds` call actions in\n order, once per version, before the snapshot every claim copies;\n `${<step id>.<output>}` passes one step\'s output to a later step. Run\n one against a live sandbox with `cofactor sandbox run <id> <action>\n --input name=... --input as=owner`.\n\n An action that signs in as a persona and creates data through the app\'s\n own API (here a cookie-session JSON API on port 8080; the port, paths and\n JSON fields are the example app\'s, use your app\'s). It needs jq and\n curl, which slim base images lack: add them to your Dockerfile\n (`apt-get install -y --no-install-recommends jq curl`).\n\n #!/usr/bin/env bash\n # sandbox/actions/create-workspace.sh\n set -euo pipefail\n login=$(jq -c --arg p "$INPUT_AS" \'.[$p] | {email, password}\' <<<"$COFACTOR_PERSONAS")\n jar=$(mktemp)\n curl -fsS -c "$jar" -H \'content-type: application/json\' \\\n -d "$login" http://127.0.0.1:8080/api/auth/login >&2\n id=$(curl -fsS -b "$jar" -H \'content-type: application/json\' \\\n -d "$(jq -nc --arg n "$INPUT_NAME" \'{name: $n}\')" \\\n http://127.0.0.1:8080/api/workspaces | jq -er \'.id\')\n jq -nc --arg id "$id" \'{workspace_id: $id}\'\n\n\nTROUBLESHOOTING\n\n - `push` says the image is not linux/amd64, or its WORKDIR can\'t be\n used: rebuild as it says.\n - The push fails with "Cofactor could not start this image": a service\n exited or never turned healthy. The message ends with that service\'s\n log. To reproduce, open a shell in the image \u2014\n docker run --rm -it --entrypoint bash <image>\n \u2014 and run that service\'s `run` command from the WORKDIR. In the\n sandbox it runs as uid 1000 with the WORKDIR made theirs; plain\n `docker run --user 1000` leaves the WORKDIR root-owned, so a write\n failure there is not the sandbox\'s.\n - A claim fails in prepare or a seed: the error carries the command\'s\n output. Fix the script, rebuild the image and push again.\n - To try prepare and actions before pushing, run the app as usual and\n call your scripts in it with the variables they read (a sandbox also\n sets the definition\'s `env:` and runs them as uid 1000):\n docker run -d --name try -p 8080:8080 <image>\n docker exec -e COFACTOR_PERSONAS=\'{"owner":{"email":"owner@example.test","password":"pw-1"}}\' try bash sandbox/personas.sh\n docker exec -e COFACTOR_PERSONAS=\'\u2026\' -e INPUT_NAME=Acme -e INPUT_AS=owner try bash sandbox/actions/create-workspace.sh\n (`docker exec` starts in the image\'s WORKDIR.)\n - `cofactor sandbox logs` and `diagnostics` are for the older sandboxes;\n for these, `cofactor sandbox show <id>` has the status and any error.\n - "push the image again": Cofactor\'s sandbox helper changed since that\n push; a fresh `cofactor sandbox push` picks up the current one.\n - A layer larger than 5 GB is refused; split or slim it.\n - Every push uploads the whole image: the build cache is missing (see\n FAST RE-PUSHES), or the Dockerfile copies the source before\n installing dependencies.\n';
|
|
84
203
|
|
|
85
204
|
// ../content/src/sandbox-v2-guide/index.ts
|
|
86
205
|
var SANDBOX_V2_GUIDE_SECTIONS = [
|
|
@@ -2047,10 +2166,42 @@ var INSTANCE_SIZE_HELP_LINES = INSTANCE_SIZES.map((size) => {
|
|
|
2047
2166
|
function printHelp() {
|
|
2048
2167
|
console.log(
|
|
2049
2168
|
[
|
|
2050
|
-
"
|
|
2169
|
+
"cofactor sandbox: Live, seeded copies of an application (Sandboxes v2), plus the legacy v1 templates and instances",
|
|
2051
2170
|
"",
|
|
2052
|
-
|
|
2053
|
-
"
|
|
2171
|
+
`Sandboxes v2 (start here: an application's cofactor.sandbox.yaml \u2014 see "cofactor sandbox guide v2"):`,
|
|
2172
|
+
" validate [file] [--upload] Offline schema + cross-check (default ./cofactor.sandbox.yaml; --upload: for pushed images)",
|
|
2173
|
+
" connect --application <a> --repo <owner/name> [--dir <path>] Build the application's sandboxes from a GitHub repo",
|
|
2174
|
+
` push --application <a> <image> [options] Push a built image as the application's next version (see "Push options" below)`,
|
|
2175
|
+
" envs --application <a> The application's delivery, version, environments, actions, operations",
|
|
2176
|
+
' claim --application <a> [options] Claim a sandbox (see "Claim options" below)',
|
|
2177
|
+
" show <id> Status, URLs, logins, seed outputs, actions and operations",
|
|
2178
|
+
' run <id> <action|plugin.op> [options] Run a repo action or emulator operation (see "Run options" below)',
|
|
2179
|
+
" destroy <id> Tear a claimed sandbox down",
|
|
2180
|
+
" verify-env --application <a> [options] Claim/wait/destroy every environment of the current version in turn",
|
|
2181
|
+
" preview --application <a> --pr <n> A pull request's preview and its logins",
|
|
2182
|
+
" guide v2 [topic] Show the Sandboxes v2 guide",
|
|
2183
|
+
` topics: ${SANDBOX_V2_GUIDE_SECTIONS.map((section) => section.slug).join(", ")}`,
|
|
2184
|
+
" (pushing a built image: start with `guide v2 upload`)",
|
|
2185
|
+
"",
|
|
2186
|
+
"Push options (Sandboxes v2):",
|
|
2187
|
+
" --definition <file> The cofactor.sandbox.yaml to push with the image (default: ./cofactor.sandbox.yaml)",
|
|
2188
|
+
" --tag <tag> Name this push (repeatable); a tag always names the newest push carrying it",
|
|
2189
|
+
" --wait Block until Cofactor has imported (booted) the image",
|
|
2190
|
+
"",
|
|
2191
|
+
"Claim options (Sandboxes v2):",
|
|
2192
|
+
' --environment <name> Environment to claim (default: "default")',
|
|
2193
|
+
" --at <version> A commit SHA, or a push id or tag (default: the default branch head / newest push)",
|
|
2194
|
+
" --wait Block until the sandbox is ready or failed, then print `show`",
|
|
2195
|
+
"",
|
|
2196
|
+
"Run options (Sandboxes v2):",
|
|
2197
|
+
" --input <key>=<value> Set an input (repeatable), coerced to the action/operation's declared type",
|
|
2198
|
+
" --json-input '{...}' Full input as JSON (overrides --input)",
|
|
2199
|
+
"",
|
|
2200
|
+
"Verify-env options (Sandboxes v2):",
|
|
2201
|
+
" --environment <name> Verify only this environment (default: every environment in the definition)",
|
|
2202
|
+
"",
|
|
2203
|
+
"Sandboxes v1 (legacy templates and instances):",
|
|
2204
|
+
" guide [section] Show the v1 sandbox authoring guide (v2: `guide v2` above)",
|
|
2054
2205
|
" list List templates and instances",
|
|
2055
2206
|
" template [id] Show template details",
|
|
2056
2207
|
" repo list --template <id> List repos linked to a template",
|
|
@@ -2086,21 +2237,6 @@ function printHelp() {
|
|
|
2086
2237
|
' action <list|get|create|delete|execute|executions> Manage template actions (see "Action sub-commands" below)',
|
|
2087
2238
|
" delete <template-id> Delete template and all instances",
|
|
2088
2239
|
"",
|
|
2089
|
-
`Sandboxes v2 (an application's cofactor.sandbox.yaml \u2014 see "cofactor sandbox guide v2"):`,
|
|
2090
|
-
" validate [file] [--upload] Offline schema + cross-check (default ./cofactor.sandbox.yaml; --upload: for pushed images)",
|
|
2091
|
-
" connect --application <a> --repo <owner/name> [--dir <path>] Build the application's sandboxes from a GitHub repo",
|
|
2092
|
-
` push --application <a> <image> [options] Push a built image as the application's next version (see "Push options" below)`,
|
|
2093
|
-
" envs --application <a> The application's delivery, version, environments, actions, operations",
|
|
2094
|
-
' claim --application <a> [options] Claim a sandbox (see "Claim options" below)',
|
|
2095
|
-
" show <id> Status, URLs, logins, seed outputs, actions and operations",
|
|
2096
|
-
' run <id> <action|plugin.op> [options] Run a repo action or emulator operation (see "Run options" below)',
|
|
2097
|
-
" destroy <id> Tear a claimed sandbox down",
|
|
2098
|
-
" verify-env --application <a> [options] Claim/wait/destroy every environment of the current version in turn",
|
|
2099
|
-
" preview --application <a> --pr <n> A pull request's preview and its logins",
|
|
2100
|
-
" guide v2 [topic] Show the Sandboxes v2 guide",
|
|
2101
|
-
` topics: ${SANDBOX_V2_GUIDE_SECTIONS.map((section) => section.slug).join(", ")}`,
|
|
2102
|
-
" (pushing a built image: start with `guide v2 upload`)",
|
|
2103
|
-
"",
|
|
2104
2240
|
"Create options:",
|
|
2105
2241
|
" --property <name|id> Property (prompted if omitted, auto-selected if only one)",
|
|
2106
2242
|
" --branch <branch> Branch (default: from linked repo)",
|
|
@@ -2119,23 +2255,6 @@ function printHelp() {
|
|
|
2119
2255
|
"Artifact get options:",
|
|
2120
2256
|
" --output <file> Write to file instead of stdout",
|
|
2121
2257
|
"",
|
|
2122
|
-
"Push options (Sandboxes v2):",
|
|
2123
|
-
" --definition <file> The cofactor.sandbox.yaml to push with the image (default: ./cofactor.sandbox.yaml)",
|
|
2124
|
-
" --tag <tag> Name this push (repeatable); a tag always names the newest push carrying it",
|
|
2125
|
-
" --wait Block until Cofactor has imported (booted) the image",
|
|
2126
|
-
"",
|
|
2127
|
-
"Claim options (Sandboxes v2):",
|
|
2128
|
-
' --environment <name> Environment to claim (default: "default")',
|
|
2129
|
-
" --at <version> A commit SHA, or a push id or tag (default: the default branch head / newest push)",
|
|
2130
|
-
" --wait Block until the sandbox is ready or failed, then print `show`",
|
|
2131
|
-
"",
|
|
2132
|
-
"Run options (Sandboxes v2):",
|
|
2133
|
-
" --input <key>=<value> Set an input (repeatable), coerced to the action/operation's declared type",
|
|
2134
|
-
" --json-input '{...}' Full input as JSON (overrides --input)",
|
|
2135
|
-
"",
|
|
2136
|
-
"Verify-env options (Sandboxes v2):",
|
|
2137
|
-
" --environment <name> Verify only this environment (default: every environment in the definition)",
|
|
2138
|
-
"",
|
|
2139
2258
|
"Logs options:",
|
|
2140
2259
|
" --service <name> Service name (default: all services; live path only)",
|
|
2141
2260
|
" --tail <n> Number of lines (default: 200; live path only)",
|
|
@@ -2144,7 +2263,7 @@ function printHelp() {
|
|
|
2144
2263
|
" Running instances auto-use live logs; failed ones auto-fall back",
|
|
2145
2264
|
" to the snapshot when the live tail can't run.",
|
|
2146
2265
|
"",
|
|
2147
|
-
"Guide sections:",
|
|
2266
|
+
"Guide sections (v1):",
|
|
2148
2267
|
" goal, research, artifacts, actions, verify, checklist",
|
|
2149
2268
|
"",
|
|
2150
2269
|
"Instance sizes:",
|
|
@@ -4169,4 +4288,4 @@ export {
|
|
|
4169
4288
|
withPluginEnabled,
|
|
4170
4289
|
withoutPlugin
|
|
4171
4290
|
};
|
|
4172
|
-
//# sourceMappingURL=sandbox-
|
|
4291
|
+
//# sourceMappingURL=sandbox-5Y4HALKW.js.map
|