insta 0.0.61 → 0.0.62
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/commands/compute.js +1 -1
- package/dist/commands/services.js +7 -3
- package/dist/github-source.js +16 -5
- package/dist/index.js +3 -2
- package/dist/template-manifest.js +24 -2
- package/package.json +1 -1
package/dist/commands/compute.js
CHANGED
|
@@ -589,7 +589,7 @@ export async function computeAlwaysOn(mode, serviceName, opts) {
|
|
|
589
589
|
if (opts.json)
|
|
590
590
|
return printJson(res.body);
|
|
591
591
|
const on = res.body.service?.always_on;
|
|
592
|
-
info(`compute ${res.body.service?.name ?? id}: always-on ${on ? 'ENABLED — machines stay warm (no cold starts; idle RAM bills at actual usage)' : 'disabled — scales to zero when idle
|
|
592
|
+
info(`compute ${res.body.service?.name ?? id}: always-on ${on ? 'ENABLED — machines stay warm (no cold starts; idle RAM bills at actual usage)' : 'disabled — scales to zero when idle'}`);
|
|
593
593
|
}
|
|
594
594
|
// ---- limits (the resource ceiling; paid plans) ----
|
|
595
595
|
// Parse a human memory value into MB: "512", "512mb", "1gb", "2g", "1.5gb".
|
|
@@ -85,7 +85,10 @@ export function servicesAddRequestBody(type, name, branch, opts) {
|
|
|
85
85
|
type, name, ...(branch ? { branch } : {}), public: !!opts.public,
|
|
86
86
|
...(opts.image ? { image: opts.image } : {}), ...(opts.port ? { port: parsePort(opts.port) } : {}),
|
|
87
87
|
...(opts.region ? { region: opts.region } : {}),
|
|
88
|
-
|
|
88
|
+
// Sent whenever the flag was given, false included: compute is born always-on by default
|
|
89
|
+
// (insta-platform #385, 2026-09-07), so `--no-always-on` must reach the API as an explicit
|
|
90
|
+
// false. Omitted means the platform default.
|
|
91
|
+
...(opts.alwaysOn !== undefined ? { alwaysOn: opts.alwaysOn } : {}),
|
|
89
92
|
...(opts.volume !== undefined ? { volumeGib: parseVolumeGib(opts.volume) } : {}),
|
|
90
93
|
};
|
|
91
94
|
}
|
|
@@ -102,8 +105,9 @@ export async function servicesAdd(type, name, opts = {}) {
|
|
|
102
105
|
throw new Error('--port is only valid for compute services');
|
|
103
106
|
parsePort(opts.port); // junk fails here, before any config/network access
|
|
104
107
|
}
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
// Presence, not truthiness: `--no-always-on` is an explicit false and is just as compute-only.
|
|
109
|
+
if (opts.alwaysOn !== undefined && type !== 'compute')
|
|
110
|
+
throw new Error('--always-on / --no-always-on is only valid for compute services (for postgres, use `insta db always-on on|off` after creation)');
|
|
107
111
|
if (opts.volume !== undefined) {
|
|
108
112
|
if (type !== 'compute')
|
|
109
113
|
throw new Error('--volume is only valid for compute services (postgres has one by default — grow it with `insta db volume --size`)');
|
package/dist/github-source.js
CHANGED
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
// credentials. Parsing, ref resolution and the shallow clone live here; the deploy path stays in
|
|
3
3
|
// commands/template.ts. See docs/superpowers/specs/2026-09-04-template-deploy-github-url-design.md.
|
|
4
4
|
import { spawn as nodeSpawn } from 'node:child_process';
|
|
5
|
-
import { mkdtempSync, rmSync, existsSync, realpathSync, statSync } from 'node:fs';
|
|
5
|
+
import { mkdtempSync, rmSync, existsSync, realpathSync, statSync, readFileSync } from 'node:fs';
|
|
6
6
|
import { tmpdir } from 'node:os';
|
|
7
7
|
import { join, sep } from 'node:path';
|
|
8
|
-
import {
|
|
8
|
+
import { parseManifestYaml, MANIFEST_FILE } from './template-manifest.js';
|
|
9
9
|
const GITHUB_HOST = /^(?:https?:\/\/)?(?:www\.)?github\.com\//i;
|
|
10
10
|
// An explicit address: a scheme, or an scp-style user@host:path. Never a local path.
|
|
11
|
-
|
|
11
|
+
// A scheme (with or without its slashes) or an scp-style user@host:path. The slashes are optional
|
|
12
|
+
// because `https:/github.com/o/r`, a URL that lost one, must be named as a bad address rather than
|
|
13
|
+
// resolved as a directory called `https:`. Two or more scheme characters are required so a Windows
|
|
14
|
+
// drive letter (`C:\src`, `C:/src`) stays a path.
|
|
15
|
+
const EXPLICIT_ADDRESS = /^[a-z][a-z0-9+.-]+:|^[^/\\]+@[^/\\]+:/i;
|
|
12
16
|
// Scheme-less first segments we still read as a host. A bare `<name>/<path>` is otherwise a LOCAL
|
|
13
17
|
// PATH: `v1.0/templates` and `my.app/bot` are directories, and reading every dotted first segment
|
|
14
18
|
// as a host broke them. Only names that unambiguously host code belong here.
|
|
@@ -251,6 +255,11 @@ export async function resolveGitHubRef(t, run) {
|
|
|
251
255
|
throw new Error(`no branch or tag ${t.refAndPath} in ${t.owner}/${t.repo}`);
|
|
252
256
|
return split;
|
|
253
257
|
}
|
|
258
|
+
/** What to call the manifest in a message: where the user pointed, never the temporary clone that
|
|
259
|
+
* is deleted before they read it. */
|
|
260
|
+
export function manifestLabel(t, r) {
|
|
261
|
+
return `${t.owner}/${t.repo}@${r.ref}:${r.path ? `${r.path}/` : ''}${MANIFEST_FILE}`;
|
|
262
|
+
}
|
|
254
263
|
export function missingManifestMessage(t, r) {
|
|
255
264
|
return [
|
|
256
265
|
`no ${MANIFEST_FILE} at ${t.owner}/${t.repo}@${r.ref}:${r.path || '/'}.`,
|
|
@@ -271,7 +280,7 @@ function containedRealPath(root, candidate) {
|
|
|
271
280
|
/** Resolve the ref, shallow-clone it, read the commit that was checked out, prove the manifest
|
|
272
281
|
* sits inside the clone, parse it, delete the clone. Nothing on disk outlives this call: not the
|
|
273
282
|
* variable prompt, not the POST, not the watcher. */
|
|
274
|
-
export async function fetchGitHubTemplate(target, run = defaultGitRunner,
|
|
283
|
+
export async function fetchGitHubTemplate(target, run = defaultGitRunner, parse = parseManifestYaml) {
|
|
275
284
|
const resolved = await resolveGitHubRef(target, run);
|
|
276
285
|
const dir = mkdtempSync(join(tmpdir(), 'insta-tpl-gh-'));
|
|
277
286
|
try {
|
|
@@ -303,7 +312,9 @@ export async function fetchGitHubTemplate(target, run = defaultGitRunner, load =
|
|
|
303
312
|
const real = containedRealPath(dir, join(manifestDir, MANIFEST_FILE));
|
|
304
313
|
if (!real || !statSync(real).isFile())
|
|
305
314
|
throw new Error(escapedManifestMessage(target, resolved));
|
|
306
|
-
|
|
315
|
+
// Read and parse separately so a validation failure names where the user pointed. Handing the
|
|
316
|
+
// loader a directory makes it report the temp path, which is gone by the time they see it.
|
|
317
|
+
const manifest = parse(readFileSync(real, 'utf8'), manifestLabel(target, resolved));
|
|
307
318
|
return {
|
|
308
319
|
source: { repo: `${target.owner}/${target.repo}`, ref: resolved.ref, path: resolved.path, commit },
|
|
309
320
|
manifest,
|
package/dist/index.js
CHANGED
|
@@ -131,7 +131,8 @@ svc.command('add [type] [name]').description('Provision a service on demand (ass
|
|
|
131
131
|
.option('--public', 'storage only: serve the bucket with anonymous public-read (default private)')
|
|
132
132
|
.option('--image <url>', 'compute only: run this container image at creation')
|
|
133
133
|
.option('--port <n>', 'compute only: port the image listens on (default 8080)')
|
|
134
|
-
.option('--always-on', 'compute only: create as always-on — never scales to zero (all plans; billing is actual usage either way)')
|
|
134
|
+
.option('--always-on', 'compute only: create as always-on — never scales to zero (the default for new compute services; all plans; billing is actual usage either way)')
|
|
135
|
+
.option('--no-always-on', 'compute only: create as scale-to-zero — idle machines suspend and wake on the next request')
|
|
135
136
|
.option('--volume <gi>', 'compute only: attach a persistent /data volume of this many whole Gi (also attachable later: `insta compute volume <name> --size <gi>`; any plan may attach at the default 10 (the free cap, on every plan); larger sizes are paid and plan-capped). Volume services keep 1 machine and stop (cold wake) instead of suspend when idle')
|
|
136
137
|
.option('--json')
|
|
137
138
|
.action(guard(async (type, name, o) => {
|
|
@@ -223,7 +224,7 @@ compute.command('status [service]').description("Show a compute service's desire
|
|
|
223
224
|
compute.command('limits [service]').description("Show or set a compute service's resource ceiling (any plan within the free cap; raising above it needs a paid plan). --memory is the dial; cpu derives from it unless --cpu is given. Billing is actual usage — the ceiling caps what the app may burn, it is not a price")
|
|
224
225
|
.option('--memory <size>', 'memory ceiling, e.g. 512mb or 1gb').option('--cpu <n>', 'vCPU ceiling override (provider sizes: 1, 2, 4, 6, 8)')
|
|
225
226
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((service, o) => computeCmd.computeLimits(service, o)));
|
|
226
|
-
compute.command('always-on <mode> [service]').description('Set a compute service always-on (mode: on|off). on = machines never scale to zero; off =
|
|
227
|
+
compute.command('always-on <mode> [service]').description('Set a compute service always-on (mode: on|off). on = machines never scale to zero (the default for new compute services); off = scale-to-zero. All plans; billing is actual usage either way')
|
|
227
228
|
.option('--json').option('--branch <branch>', 'branch (default: current)').action(guard((mode, service, o) => computeCmd.computeAlwaysOn(mode, service, o)));
|
|
228
229
|
const execCmd = compute.command('exec [service]').description("Run a one-shot command inside a compute service's machine (`insta compute exec [service] -- <command> [args…]`) — no interactive shell/PTY: `command` is argv, no shell is invoked (use [\"sh\", \"-c\", \"...\"] for shell features). Wakes the machine first if it's scaled to zero — expect a few seconds of latency, billed as uptime, not an error. Exits with the remote command's own exit code (agents rely on this)")
|
|
229
230
|
.action(guard((service, o) => computeCmd.computeExec(service, execCommand, o, { windowsFallback: execWindowsFallback })));
|
|
@@ -78,8 +78,30 @@ export function validateManifest(m) {
|
|
|
78
78
|
for (const name of names) {
|
|
79
79
|
const svc = services[name] ?? {};
|
|
80
80
|
const where = `services.${name}`;
|
|
81
|
-
if (svc.type !== 'web' && svc.type !== 'worker')
|
|
82
|
-
problems.push(`${where}.type must be web or
|
|
81
|
+
if (svc.type !== 'web' && svc.type !== 'worker' && svc.type !== 'postgres') {
|
|
82
|
+
problems.push(`${where}.type must be web, worker or postgres`);
|
|
83
|
+
}
|
|
84
|
+
// A managed postgres service is BARE: the platform owns its image, port, sizing, credentials
|
|
85
|
+
// and env, so every other rule below would be asking about fields it must not carry. Mirrors
|
|
86
|
+
// the platform's own check (provisioning/templateManifest.ts) so an author hears it here.
|
|
87
|
+
if (svc.type === 'postgres') {
|
|
88
|
+
const bare = svc;
|
|
89
|
+
for (const field of ['image', 'build', 'port', 'healthcheck', 'volume', 'volumeGib', 'spec', 'alwaysOn']) {
|
|
90
|
+
if (bare[field] !== undefined) {
|
|
91
|
+
problems.push(`${where}.${field}: a postgres service is platform-managed and carries no ${field} — declare it bare ({ type: postgres })`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const groups = ['fixed', 'generated', 'platform', 'required', 'optional'];
|
|
95
|
+
const envShell = bare.env;
|
|
96
|
+
if (envShell !== undefined) {
|
|
97
|
+
const emptyShell = !!envShell && typeof envShell === 'object' && !Array.isArray(envShell)
|
|
98
|
+
&& Object.entries(envShell).every(([g, v]) => groups.includes(g) && !!v && typeof v === 'object' && !Array.isArray(v) && Object.keys(v).length === 0);
|
|
99
|
+
if (!emptyShell) {
|
|
100
|
+
problems.push(`${where}.env: a postgres service is platform-managed and carries no env — declare it bare ({ type: postgres })`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
83
105
|
if (svc.image && svc.build)
|
|
84
106
|
problems.push(`${where}: image and build are mutually exclusive`);
|
|
85
107
|
if (!svc.image && !svc.build)
|