@astrale-os/sdk 0.2.0 → 0.3.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/LICENSE +202 -0
- package/dist/auth/check.d.ts +1 -2
- package/dist/auth/check.d.ts.map +1 -1
- package/dist/auth/check.js +2 -6
- package/dist/auth/check.js.map +1 -1
- package/dist/auth/index.d.ts +2 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +1 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/issuer-mint.d.ts +53 -0
- package/dist/auth/issuer-mint.d.ts.map +1 -0
- package/dist/auth/issuer-mint.js +35 -0
- package/dist/auth/issuer-mint.js.map +1 -0
- package/dist/auth/kernel-client.d.ts +8 -2
- package/dist/auth/kernel-client.d.ts.map +1 -1
- package/dist/auth/kernel-client.js +22 -46
- package/dist/auth/kernel-client.js.map +1 -1
- package/dist/cli/dotenv.d.ts +8 -0
- package/dist/cli/dotenv.d.ts.map +1 -1
- package/dist/cli/dotenv.js +18 -0
- package/dist/cli/dotenv.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/log.d.ts +11 -0
- package/dist/cli/log.d.ts.map +1 -0
- package/dist/cli/log.js +17 -0
- package/dist/cli/log.js.map +1 -0
- package/dist/cli/publish.d.ts +35 -0
- package/dist/cli/publish.d.ts.map +1 -0
- package/dist/cli/publish.js +271 -0
- package/dist/cli/publish.js.map +1 -0
- package/dist/cli/run.d.ts +17 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +85 -101
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/spec.d.ts.map +1 -1
- package/dist/cli/spec.js +1 -0
- package/dist/cli/spec.js.map +1 -1
- package/dist/config/define-domain.d.ts +79 -2
- package/dist/config/define-domain.d.ts.map +1 -1
- package/dist/config/define-domain.js +58 -1
- package/dist/config/define-domain.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/define/remote-function.d.ts +23 -3
- package/dist/define/remote-function.d.ts.map +1 -1
- package/dist/define/remote-function.js +1 -1
- package/dist/define/remote-function.js.map +1 -1
- package/dist/deploy/check.d.ts +6 -6
- package/dist/deploy/check.d.ts.map +1 -1
- package/dist/deploy/check.js +1 -1
- package/dist/deploy/check.js.map +1 -1
- package/dist/deploy/index.d.ts +0 -1
- package/dist/deploy/index.d.ts.map +1 -1
- package/dist/deploy/index.js +0 -1
- package/dist/deploy/index.js.map +1 -1
- package/dist/deploy/meta.d.ts +11 -0
- package/dist/deploy/meta.d.ts.map +1 -1
- package/dist/deploy/meta.js +23 -0
- package/dist/deploy/meta.js.map +1 -1
- package/dist/dispatch/dispatcher.d.ts +17 -4
- package/dist/dispatch/dispatcher.d.ts.map +1 -1
- package/dist/dispatch/dispatcher.js +15 -3
- package/dist/dispatch/dispatcher.js.map +1 -1
- package/dist/dispatch/execute.d.ts +6 -2
- package/dist/dispatch/execute.d.ts.map +1 -1
- package/dist/dispatch/execute.js +1 -0
- package/dist/dispatch/execute.js.map +1 -1
- package/dist/dispatch/identity.d.ts +11 -10
- package/dist/dispatch/identity.d.ts.map +1 -1
- package/dist/dispatch/identity.js +4 -3
- package/dist/dispatch/identity.js.map +1 -1
- package/dist/dispatch/index.d.ts +1 -1
- package/dist/dispatch/index.d.ts.map +1 -1
- package/dist/dispatch/index.js +1 -1
- package/dist/dispatch/index.js.map +1 -1
- package/dist/dispatch/self.d.ts +25 -37
- package/dist/dispatch/self.d.ts.map +1 -1
- package/dist/dispatch/self.js +60 -30
- package/dist/dispatch/self.js.map +1 -1
- package/dist/domain/build-spec.d.ts.map +1 -1
- package/dist/domain/build-spec.js +15 -3
- package/dist/domain/build-spec.js.map +1 -1
- package/dist/domain/define.d.ts +17 -32
- package/dist/domain/define.d.ts.map +1 -1
- package/dist/domain/define.js +12 -31
- package/dist/domain/define.js.map +1 -1
- package/dist/domain/extend-functions.d.ts +13 -9
- package/dist/domain/extend-functions.d.ts.map +1 -1
- package/dist/domain/extend-functions.js +28 -13
- package/dist/domain/extend-functions.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/method/class.d.ts +2 -2
- package/dist/method/class.d.ts.map +1 -1
- package/dist/method/class.js.map +1 -1
- package/dist/method/context.d.ts +18 -3
- package/dist/method/context.d.ts.map +1 -1
- package/dist/method/index.d.ts +2 -2
- package/dist/method/index.d.ts.map +1 -1
- package/dist/method/index.js.map +1 -1
- package/dist/method/single.d.ts +25 -38
- package/dist/method/single.d.ts.map +1 -1
- package/dist/method/single.js +1 -11
- package/dist/method/single.js.map +1 -1
- package/dist/server/auxiliary-routes.d.ts.map +1 -1
- package/dist/server/auxiliary-routes.js +13 -2
- package/dist/server/auxiliary-routes.js.map +1 -1
- package/dist/server/create.d.ts +2 -15
- package/dist/server/create.d.ts.map +1 -1
- package/dist/server/create.js +15 -51
- package/dist/server/create.js.map +1 -1
- package/dist/server/domain-entry.d.ts +10 -0
- package/dist/server/domain-entry.d.ts.map +1 -1
- package/dist/server/domain-entry.js +2 -0
- package/dist/server/domain-entry.js.map +1 -1
- package/dist/server/index.d.ts +0 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +0 -1
- package/dist/server/index.js.map +1 -1
- package/package.json +10 -8
- package/src/auth/check.ts +2 -6
- package/src/auth/index.ts +2 -0
- package/src/auth/issuer-mint.ts +75 -0
- package/src/auth/kernel-client.ts +31 -63
- package/src/cli/dotenv.ts +20 -0
- package/src/cli/index.ts +1 -1
- package/src/cli/log.ts +18 -0
- package/src/cli/publish.ts +341 -0
- package/src/cli/run.ts +92 -113
- package/src/cli/spec.ts +1 -0
- package/src/config/define-domain.ts +158 -5
- package/src/config/index.ts +1 -1
- package/src/define/remote-function.ts +47 -7
- package/src/deploy/check.ts +7 -7
- package/src/deploy/index.ts +0 -1
- package/src/deploy/meta.ts +25 -0
- package/src/dispatch/dispatcher.ts +32 -9
- package/src/dispatch/execute.ts +7 -2
- package/src/dispatch/identity.ts +11 -10
- package/src/dispatch/index.ts +1 -1
- package/src/dispatch/self.ts +117 -41
- package/src/domain/build-spec.ts +15 -1
- package/src/domain/define.ts +26 -38
- package/src/domain/extend-functions.ts +35 -15
- package/src/index.ts +29 -5
- package/src/method/class.ts +11 -7
- package/src/method/context.ts +19 -3
- package/src/method/index.ts +8 -2
- package/src/method/single.ts +35 -42
- package/src/server/auxiliary-routes.ts +13 -2
- package/src/server/create.ts +15 -51
- package/src/server/domain-entry.ts +12 -0
- package/src/server/index.ts +0 -1
- package/src/deploy/hash-spec.ts +0 -31
- package/src/server/worker-meta.ts +0 -25
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All `publish` logic — the schema-publish step, the admin-catalog register, and
|
|
3
|
+
* the release orchestration tying them together. `run.ts` only parses args and
|
|
4
|
+
* dispatches here.
|
|
5
|
+
*
|
|
6
|
+
* A release has two halves that must stay in sync: the CONTRACT (the `./schema`
|
|
7
|
+
* package on npm, so other domains can `imports`/`implements`/`extends` it) and
|
|
8
|
+
* the DEPLOYMENT (the worker URL in the admin catalog). They run npm-FIRST — npm
|
|
9
|
+
* publish is irreversible, so the catalog is registered only on schema-publish
|
|
10
|
+
* success, never pointing at an unpublished contract.
|
|
11
|
+
*
|
|
12
|
+
* Why `pnpm pack` then `npm publish <tarball>` (not a plain `npm publish`): a
|
|
13
|
+
* schema package ships only `dist` and uses `publishConfig` to repoint
|
|
14
|
+
* main/exports from source `.ts` to `dist` — a field override only PNPM applies,
|
|
15
|
+
* so npm would otherwise publish an entry pointing at unshipped source.
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'node:child_process'
|
|
18
|
+
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from 'node:fs'
|
|
19
|
+
import { tmpdir } from 'node:os'
|
|
20
|
+
import { join } from 'node:path'
|
|
21
|
+
|
|
22
|
+
import { BOLD, DIM, error, GREEN, info, RESET, warn } from './log'
|
|
23
|
+
|
|
24
|
+
type SchemaPkg = {
|
|
25
|
+
name?: string
|
|
26
|
+
version?: string
|
|
27
|
+
private?: boolean
|
|
28
|
+
files?: readonly string[]
|
|
29
|
+
peerDependencies?: Record<string, string>
|
|
30
|
+
dependencies?: Record<string, string>
|
|
31
|
+
optionalDependencies?: Record<string, string>
|
|
32
|
+
publishConfig?: { access?: string }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── release orchestration (schema THEN catalog) ──────────────────────────────
|
|
36
|
+
|
|
37
|
+
/** `publish` (no deploy): publish the schema, then register the deployed URL. */
|
|
38
|
+
export async function runRelease(opts: {
|
|
39
|
+
projectDir: string
|
|
40
|
+
origin: string
|
|
41
|
+
name?: string
|
|
42
|
+
publicUrl?: string
|
|
43
|
+
skipSchema: boolean
|
|
44
|
+
dryRun: boolean
|
|
45
|
+
otp?: string
|
|
46
|
+
installByDefault?: boolean
|
|
47
|
+
}): Promise<number> {
|
|
48
|
+
const schemaCode = await publishSchemaBeforeCatalog(opts.projectDir, opts)
|
|
49
|
+
if (schemaCode !== 0) return schemaCode
|
|
50
|
+
|
|
51
|
+
const url = opts.publicUrl ?? `https://${opts.origin}`
|
|
52
|
+
if (opts.dryRun) {
|
|
53
|
+
info(`dry-run: would register ${opts.origin} → ${url} in the admin catalog (not registered).`)
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
info(`registering ${opts.origin} → ${url} in the admin catalog (no deploy)`)
|
|
57
|
+
return await registerCatalog(opts.projectDir, opts, url)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The `deploy --publish` tail: publish the schema, then register the just-deployed URL. */
|
|
61
|
+
export async function runReleaseAfterDeploy(opts: {
|
|
62
|
+
projectDir: string
|
|
63
|
+
origin: string
|
|
64
|
+
deployedUrl: string
|
|
65
|
+
name?: string
|
|
66
|
+
skipSchema: boolean
|
|
67
|
+
otp?: string
|
|
68
|
+
installByDefault?: boolean
|
|
69
|
+
}): Promise<number> {
|
|
70
|
+
const schemaCode = await publishSchemaBeforeCatalog(opts.projectDir, { ...opts, dryRun: false })
|
|
71
|
+
if (schemaCode !== 0) return schemaCode
|
|
72
|
+
return await registerCatalog(opts.projectDir, opts, opts.deployedUrl)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The schema half of a release. npm publish is irreversible, so it runs FIRST:
|
|
77
|
+
* returns 0 to proceed (published | skipped | no `./schema` package), non-zero to
|
|
78
|
+
* ABORT before the catalog touch — so the catalog never points at an unpublished
|
|
79
|
+
* contract.
|
|
80
|
+
*/
|
|
81
|
+
async function publishSchemaBeforeCatalog(
|
|
82
|
+
projectDir: string,
|
|
83
|
+
opts: { skipSchema: boolean; dryRun: boolean; otp?: string },
|
|
84
|
+
): Promise<number> {
|
|
85
|
+
if (opts.skipSchema) return 0
|
|
86
|
+
if (!existsSync(join(projectDir, 'schema', 'package.json'))) {
|
|
87
|
+
info('no ./schema package — registering the catalog only.')
|
|
88
|
+
return 0
|
|
89
|
+
}
|
|
90
|
+
const code = await publishSchema({
|
|
91
|
+
projectDir,
|
|
92
|
+
schemaDir: 'schema',
|
|
93
|
+
dryRun: opts.dryRun,
|
|
94
|
+
...(opts.otp !== undefined ? { otp: opts.otp } : {}),
|
|
95
|
+
})
|
|
96
|
+
if (code !== 0)
|
|
97
|
+
error(
|
|
98
|
+
'schema publish failed — NOT registering the catalog (a catalog entry must never point ' +
|
|
99
|
+
'at an unpublished contract). Fix the schema package and re-run, or pass --skip-schema.',
|
|
100
|
+
)
|
|
101
|
+
return code
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Register a domain's public URL in the admin catalog by shelling out to the
|
|
106
|
+
* operator CLI (`astrale domain publish`). Deliberately a subprocess: all
|
|
107
|
+
* credential/admin-target resolution lives in `@astrale-os/cli`, kept out of
|
|
108
|
+
* this build tool. A missing `astrale` is reported with the exact line to run by
|
|
109
|
+
* hand. The catalog `name` is a short slug (the origin's first label by default),
|
|
110
|
+
* distinct from the FQDN origin and from any `@scope/…` npm package name.
|
|
111
|
+
*/
|
|
112
|
+
async function registerCatalog(
|
|
113
|
+
projectDir: string,
|
|
114
|
+
opts: { origin: string; name?: string; installByDefault?: boolean },
|
|
115
|
+
url: string,
|
|
116
|
+
): Promise<number> {
|
|
117
|
+
const { description } = readPackageMeta(projectDir)
|
|
118
|
+
const name = opts.name ?? opts.origin.split('.')[0] ?? opts.origin
|
|
119
|
+
// `--public-url`, not `--url`: the operator CLI reserves `--url` for kernel targeting.
|
|
120
|
+
const cliArgs = [
|
|
121
|
+
'domain',
|
|
122
|
+
'publish',
|
|
123
|
+
'--origin',
|
|
124
|
+
opts.origin,
|
|
125
|
+
'--name',
|
|
126
|
+
name,
|
|
127
|
+
'--public-url',
|
|
128
|
+
url,
|
|
129
|
+
]
|
|
130
|
+
if (description) cliArgs.push('--description', description)
|
|
131
|
+
if (opts.installByDefault) cliArgs.push('--install-by-default')
|
|
132
|
+
|
|
133
|
+
info(`publishing to the admin catalog → astrale ${cliArgs.join(' ')}`)
|
|
134
|
+
return await new Promise<number>((resolve) => {
|
|
135
|
+
const child = spawn('astrale', cliArgs, { stdio: 'inherit' })
|
|
136
|
+
child.on('error', (err) => {
|
|
137
|
+
if ((err as NodeJS.ErrnoException).code === 'ENOENT')
|
|
138
|
+
error(
|
|
139
|
+
`\`astrale\` CLI not found on PATH. Register it by hand:\n astrale ${cliArgs.join(' ')}`,
|
|
140
|
+
)
|
|
141
|
+
else error(`failed to run \`astrale domain publish\`: ${err.message}`)
|
|
142
|
+
resolve(1)
|
|
143
|
+
})
|
|
144
|
+
child.on('exit', (code) => resolve(code ?? 1))
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function readPackageMeta(projectDir: string): { name?: string; description?: string } {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8')) as {
|
|
151
|
+
name?: string
|
|
152
|
+
description?: string
|
|
153
|
+
}
|
|
154
|
+
} catch {
|
|
155
|
+
return {}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ── schema publish ───────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Build a domain's thin schema package and publish it to npm. Operates on the
|
|
163
|
+
* schema PACKAGE (its own package.json), not astrale.config.ts, so it needs no
|
|
164
|
+
* adapter — `publish --schema` runs it config-free.
|
|
165
|
+
*/
|
|
166
|
+
export async function publishSchema(args: {
|
|
167
|
+
projectDir: string
|
|
168
|
+
/** Schema-package dir. Default: `<projectDir>/schema`, else `<projectDir>`. */
|
|
169
|
+
schemaDir?: string
|
|
170
|
+
dryRun: boolean
|
|
171
|
+
/** 2FA one-time password forwarded to `npm publish`. */
|
|
172
|
+
otp?: string
|
|
173
|
+
}): Promise<number> {
|
|
174
|
+
const dir = resolveSchemaDir(args.projectDir, args.schemaDir)
|
|
175
|
+
if (!dir) {
|
|
176
|
+
error(
|
|
177
|
+
`No schema package found (no package.json at ./schema or the current dir). A schema\n` +
|
|
178
|
+
` package declares peerDependencies (@astrale-os/kernel-core + kernel-dsl + zod) and a\n` +
|
|
179
|
+
` publishConfig that repoints main/exports to ./dist — see domains/workspace/schema.`,
|
|
180
|
+
)
|
|
181
|
+
return 1
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const pkgPath = join(dir, 'package.json')
|
|
185
|
+
let pkg: SchemaPkg
|
|
186
|
+
try {
|
|
187
|
+
pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as SchemaPkg
|
|
188
|
+
} catch (err) {
|
|
189
|
+
error(`Could not read ${pkgPath}: ${(err as Error).message}`)
|
|
190
|
+
return 1
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const problems = preflight(pkg)
|
|
194
|
+
if (problems.length > 0) {
|
|
195
|
+
error(`Schema package is not ready to publish (${pkgPath}):`)
|
|
196
|
+
for (const p of problems) process.stderr.write(` • ${p}\n`)
|
|
197
|
+
return 1
|
|
198
|
+
}
|
|
199
|
+
const name = pkg.name as string
|
|
200
|
+
const version = pkg.version as string
|
|
201
|
+
|
|
202
|
+
// Idempotent in BOTH modes: a dry-run must skip too, since `npm publish
|
|
203
|
+
// --dry-run` still validates against the registry and errors on an existing version.
|
|
204
|
+
if (await isOnRegistry(name, version)) {
|
|
205
|
+
info(
|
|
206
|
+
`${BOLD}${name}@${version}${RESET} is already published — ` +
|
|
207
|
+
(args.dryRun ? `a real run would skip it.` : `bump the version to ship again.`),
|
|
208
|
+
)
|
|
209
|
+
return 0
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const dest = mkdtempSync(join(tmpdir(), 'astrale-schema-pack-'))
|
|
213
|
+
try {
|
|
214
|
+
info(`packing ${BOLD}${name}@${version}${RESET} from ${rel(args.projectDir, dir)}`)
|
|
215
|
+
if ((await runInherit('pnpm', ['pack', '--pack-destination', dest], dir)) !== 0) {
|
|
216
|
+
error(`pnpm pack failed — see the build output above.`)
|
|
217
|
+
return 1
|
|
218
|
+
}
|
|
219
|
+
const tarball = readdirSync(dest)
|
|
220
|
+
.filter((f) => f.endsWith('.tgz'))
|
|
221
|
+
.map((f) => join(dest, f))[0]
|
|
222
|
+
if (!tarball) {
|
|
223
|
+
error(`pnpm pack produced no .tgz in ${dest}.`)
|
|
224
|
+
return 1
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// `--access` mirrors the manifest's baked publishConfig.access; npm's 2FA
|
|
228
|
+
// prompt passes through `stdio: 'inherit'` (or `--otp` for non-interactive).
|
|
229
|
+
const access = pkg.publishConfig?.access ?? 'public'
|
|
230
|
+
const publishArgs = [
|
|
231
|
+
'publish',
|
|
232
|
+
tarball,
|
|
233
|
+
'--access',
|
|
234
|
+
access,
|
|
235
|
+
...(args.dryRun ? ['--dry-run'] : []),
|
|
236
|
+
...(args.otp ? ['--otp', args.otp] : []),
|
|
237
|
+
]
|
|
238
|
+
info(
|
|
239
|
+
args.dryRun
|
|
240
|
+
? `dry-run: ${DIM}npm ${publishArgs.join(' ')}${RESET}`
|
|
241
|
+
: `publishing ${DIM}(a browser / OTP prompt may appear for 2FA)${RESET}`,
|
|
242
|
+
)
|
|
243
|
+
const code = await runInherit('npm', publishArgs)
|
|
244
|
+
if (code !== 0) {
|
|
245
|
+
error(
|
|
246
|
+
`npm publish exited ${code}. An auth error (E401/E404) usually means the session ` +
|
|
247
|
+
`expired — \`npm login\` and retry.`,
|
|
248
|
+
)
|
|
249
|
+
return code
|
|
250
|
+
}
|
|
251
|
+
if (args.dryRun) info(`dry-run complete — nothing was published.`)
|
|
252
|
+
else printPublished(name, version)
|
|
253
|
+
return 0
|
|
254
|
+
} finally {
|
|
255
|
+
try {
|
|
256
|
+
rmSync(dest, { recursive: true, force: true })
|
|
257
|
+
} catch {
|
|
258
|
+
warn(`(non-fatal) could not clean the temp pack dir ${dest}`)
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Locate the schema package: an explicit path, else `<projectDir>/schema`, else `<projectDir>`. */
|
|
264
|
+
function resolveSchemaDir(projectDir: string, explicit?: string): string | undefined {
|
|
265
|
+
const candidates = explicit
|
|
266
|
+
? [join(projectDir, explicit)]
|
|
267
|
+
: [join(projectDir, 'schema'), projectDir]
|
|
268
|
+
return candidates.find((c) => existsSync(join(c, 'package.json')))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/** Static guards against the classic schema-publish footguns, before any network call. */
|
|
272
|
+
function preflight(pkg: SchemaPkg): string[] {
|
|
273
|
+
const problems: string[] = []
|
|
274
|
+
if (!pkg.name) problems.push('package.json has no "name".')
|
|
275
|
+
if (!pkg.version) problems.push('package.json has no "version".')
|
|
276
|
+
else if (!/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(pkg.version))
|
|
277
|
+
problems.push(`"version" ("${pkg.version}") must be an exact semver, not a range.`)
|
|
278
|
+
if (pkg.private) problems.push('"private": true — remove it to publish.')
|
|
279
|
+
// Peers, not deps: bundling kernel-core/dsl/zod gives the consumer a 2nd copy
|
|
280
|
+
// and breaks the def `__brand` identity at compile time.
|
|
281
|
+
if (!pkg.peerDependencies || Object.keys(pkg.peerDependencies).length === 0)
|
|
282
|
+
problems.push(
|
|
283
|
+
'no "peerDependencies" — peer-depend on @astrale-os/kernel-core (+ kernel-dsl, zod).',
|
|
284
|
+
)
|
|
285
|
+
if (!pkg.files?.includes('dist'))
|
|
286
|
+
problems.push('"files" must include "dist" (ship only the built output).')
|
|
287
|
+
if (!pkg.publishConfig)
|
|
288
|
+
problems.push('no "publishConfig" — main/types/exports won\'t repoint to ./dist at pack time.')
|
|
289
|
+
for (const field of ['dependencies', 'peerDependencies', 'optionalDependencies'] as const) {
|
|
290
|
+
for (const [dep, spec] of Object.entries(pkg[field] ?? {})) {
|
|
291
|
+
if (/^(link:|file:|workspace:)/.test(spec))
|
|
292
|
+
problems.push(
|
|
293
|
+
`${field}["${dep}"] is "${spec}" — npm can't publish that; use a semver range.`,
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
return problems
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** True if `<name>@<version>` already exists on the registry. */
|
|
301
|
+
async function isOnRegistry(name: string, version: string): Promise<boolean> {
|
|
302
|
+
return await new Promise<boolean>((resolve) => {
|
|
303
|
+
let out = ''
|
|
304
|
+
const child = spawn('npm', ['view', `${name}@${version}`, 'version'], {
|
|
305
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
306
|
+
})
|
|
307
|
+
child.stdout?.on('data', (d) => (out += String(d)))
|
|
308
|
+
child.on('error', () => resolve(false)) // npm missing / not-found / offline → let publish surface it
|
|
309
|
+
child.on('exit', () => resolve(out.trim().length > 0))
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function runInherit(cmd: string, cmdArgs: readonly string[], cwd?: string): Promise<number> {
|
|
314
|
+
return await new Promise<number>((resolve) => {
|
|
315
|
+
const child = spawn(cmd, cmdArgs as string[], { stdio: 'inherit', ...(cwd ? { cwd } : {}) })
|
|
316
|
+
child.on('error', (err) => {
|
|
317
|
+
const c = (err as NodeJS.ErrnoException).code
|
|
318
|
+
error(
|
|
319
|
+
c === 'ENOENT'
|
|
320
|
+
? `\`${cmd}\` not found on PATH.`
|
|
321
|
+
: `failed to run \`${cmd}\`: ${err.message}`,
|
|
322
|
+
)
|
|
323
|
+
resolve(1)
|
|
324
|
+
})
|
|
325
|
+
child.on('exit', (code) => resolve(code ?? 1))
|
|
326
|
+
})
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function printPublished(name: string, version: string): void {
|
|
330
|
+
process.stdout.write(
|
|
331
|
+
`\n${GREEN}✓${RESET} published ${BOLD}${name}@${version}${RESET}\n` +
|
|
332
|
+
` ${DIM}consume it:${RESET} ${BOLD}import { schema as S } from '${name}'${RESET}` +
|
|
333
|
+
`${DIM} → imports: [S], implements: [S.interfaces.X]${RESET}\n` +
|
|
334
|
+
` ${DIM}and add ${BOLD}requires: ['<this-origin>']${RESET}${DIM} on the consumer ` +
|
|
335
|
+
`(producer installs first).${RESET}\n\n`,
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function rel(from: string, to: string): string {
|
|
340
|
+
return to.startsWith(from) ? `.${to.slice(from.length) || '/'}` : to
|
|
341
|
+
}
|
package/src/cli/run.ts
CHANGED
|
@@ -18,7 +18,15 @@
|
|
|
18
18
|
* astrale-domain prod # = deploy prod
|
|
19
19
|
* astrale-domain deploy <env> # any env key
|
|
20
20
|
* astrale-domain build # rebuild spec only (placeholder URL)
|
|
21
|
-
* astrale-domain publish [env] #
|
|
21
|
+
* astrale-domain publish [env] # RELEASE: publish ./schema to npm (if any) + register the deployed URL
|
|
22
|
+
*
|
|
23
|
+
* A release has two halves that must stay in sync: the CONTRACT (`./schema` on
|
|
24
|
+
* npm) and the DEPLOYMENT (worker URL in the catalog). `publish` does both,
|
|
25
|
+
* npm-FIRST — `npm publish` is irreversible, so the catalog is registered only on
|
|
26
|
+
* schema-publish success, never pointing at an unpublished contract. The schema
|
|
27
|
+
* publish is idempotent (skips an unbumped version). Scope it with `--schema`
|
|
28
|
+
* (contract only — config-free) or `--skip-schema` (catalog only). All publish
|
|
29
|
+
* logic lives in `./publish.ts`; this file only parses args and dispatches.
|
|
22
30
|
*
|
|
23
31
|
* `publish` registers a domain's ALREADY-deployed URL in the admin catalog by
|
|
24
32
|
* SHELLING OUT to the operator CLI (`astrale domain publish --origin --name
|
|
@@ -31,8 +39,7 @@
|
|
|
31
39
|
* already points you at for `domain install`).
|
|
32
40
|
*/
|
|
33
41
|
|
|
34
|
-
import {
|
|
35
|
-
import { existsSync, readFileSync, watch as watchFs } from 'node:fs'
|
|
42
|
+
import { existsSync, watch as watchFs } from 'node:fs'
|
|
36
43
|
import { mkdir, writeFile } from 'node:fs/promises'
|
|
37
44
|
import { createRequire } from 'node:module'
|
|
38
45
|
import { basename, dirname, isAbsolute, join } from 'node:path'
|
|
@@ -41,7 +48,9 @@ import { pathToFileURL } from 'node:url'
|
|
|
41
48
|
import type { DeployResult, DomainAdapter, DomainInfo, WatchHandle } from '../config/adapter'
|
|
42
49
|
import type { DeployConfig } from '../config/deploy'
|
|
43
50
|
|
|
44
|
-
import {
|
|
51
|
+
import { loadDeclaredSecrets } from './dotenv'
|
|
52
|
+
import { BOLD, DIM, error, GREEN, info, RESET, warn } from './log'
|
|
53
|
+
import { publishSchema, runRelease, runReleaseAfterDeploy } from './publish'
|
|
45
54
|
import { buildProjectSpec } from './spec'
|
|
46
55
|
|
|
47
56
|
const CONFIG_NAMES = ['astrale.config.ts', 'astrale.config.js', 'astrale.config.mjs']
|
|
@@ -50,6 +59,14 @@ type ParsedArgs = {
|
|
|
50
59
|
command: 'dev' | 'build' | 'deploy' | 'publish'
|
|
51
60
|
env: string
|
|
52
61
|
watch: boolean
|
|
62
|
+
/** `--dry-run` (publish) — pack + `npm publish --dry-run` and skip the catalog register. */
|
|
63
|
+
dryRun?: boolean
|
|
64
|
+
/** `--schema` (publish) — publish ONLY the `./schema` package; no catalog, no config. */
|
|
65
|
+
schemaOnly?: boolean
|
|
66
|
+
/** `--otp <code>` (publish / deploy --publish) — 2FA OTP forwarded to `npm publish`. */
|
|
67
|
+
otp?: string
|
|
68
|
+
/** `--skip-schema` (publish / deploy --publish) — register the catalog only; don't publish `./schema`. */
|
|
69
|
+
skipSchema?: boolean
|
|
53
70
|
/** `--port <n>` (dev only) — overrides the env's local dev port. */
|
|
54
71
|
port?: number
|
|
55
72
|
/** `--host <url>` (dev only) — public URL of a tunnel/proxy front: binds 0.0.0.0 + pins WORKER_URL. */
|
|
@@ -79,6 +96,17 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
79
96
|
return 2
|
|
80
97
|
}
|
|
81
98
|
|
|
99
|
+
// `publish --schema` (schema only) operates on the schema PACKAGE, not
|
|
100
|
+
// astrale.config.ts — handle it before config discovery so it works in a folder
|
|
101
|
+
// that is purely a schema package and needs no adapter/Bun runtime.
|
|
102
|
+
if (parsed.command === 'publish' && parsed.schemaOnly) {
|
|
103
|
+
return await publishSchema({
|
|
104
|
+
projectDir: process.cwd(),
|
|
105
|
+
dryRun: parsed.dryRun ?? false,
|
|
106
|
+
...(parsed.otp !== undefined ? { otp: parsed.otp } : {}),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
82
110
|
// The CLI imports the project's TypeScript modules (astrale.config.ts, the ★
|
|
83
111
|
// files) directly — that requires a TS-native runtime. Fail with the remedy
|
|
84
112
|
// instead of a cryptic ERR_UNKNOWN_FILE_EXTENSION deep in `import()`.
|
|
@@ -137,20 +165,16 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
if (parsed.command === 'publish') {
|
|
140
|
-
//
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// only points the registry at an address the author already deployed.
|
|
145
|
-
const pkg = readPackageMeta(projectDir)
|
|
146
|
-
const name = parsed.name ?? def.origin.split('.')[0] ?? def.origin
|
|
147
|
-
const url = parsed.publicUrl ?? `https://${def.origin}`
|
|
148
|
-
info(`registering ${def.origin} → ${url} in the admin catalog (no deploy)`)
|
|
149
|
-
return await publishToAdmin({
|
|
168
|
+
// `--schema` (schema only) was handled before config discovery; this is the
|
|
169
|
+
// full release (schema + catalog) or `--skip-schema` (catalog only).
|
|
170
|
+
return await runRelease({
|
|
171
|
+
projectDir,
|
|
150
172
|
origin: def.origin,
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
...(
|
|
173
|
+
skipSchema: parsed.skipSchema ?? false,
|
|
174
|
+
dryRun: parsed.dryRun ?? false,
|
|
175
|
+
...(parsed.name !== undefined ? { name: parsed.name } : {}),
|
|
176
|
+
...(parsed.publicUrl !== undefined ? { publicUrl: parsed.publicUrl } : {}),
|
|
177
|
+
...(parsed.otp !== undefined ? { otp: parsed.otp } : {}),
|
|
154
178
|
...(parsed.installByDefault ? { installByDefault: true } : {}),
|
|
155
179
|
})
|
|
156
180
|
}
|
|
@@ -205,84 +229,21 @@ export async function run(argv: readonly string[]): Promise<number> {
|
|
|
205
229
|
printDeployed(result, def)
|
|
206
230
|
|
|
207
231
|
if (parsed.publish) {
|
|
208
|
-
|
|
209
|
-
//
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// convention, e.g. `ai-gateway`); `--name` overrides.
|
|
213
|
-
const name = parsed.name ?? def.origin.split('.')[0] ?? def.origin
|
|
214
|
-
return await publishToAdmin({
|
|
232
|
+
// `--publish` = release after deploy: ship the schema (if any) THEN register
|
|
233
|
+
// the just-deployed URL — same npm-first ordering as `publish`.
|
|
234
|
+
return await runReleaseAfterDeploy({
|
|
235
|
+
projectDir,
|
|
215
236
|
origin: def.origin,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
...(
|
|
237
|
+
deployedUrl: result.url,
|
|
238
|
+
skipSchema: parsed.skipSchema ?? false,
|
|
239
|
+
...(parsed.name !== undefined ? { name: parsed.name } : {}),
|
|
240
|
+
...(parsed.otp !== undefined ? { otp: parsed.otp } : {}),
|
|
219
241
|
...(parsed.installByDefault ? { installByDefault: true } : {}),
|
|
220
242
|
})
|
|
221
243
|
}
|
|
222
244
|
return 0
|
|
223
245
|
}
|
|
224
246
|
|
|
225
|
-
/**
|
|
226
|
-
* Register the just-deployed URL in the admin catalog by shelling out to the
|
|
227
|
-
* operator CLI (`astrale domain publish`). Deliberately a subprocess, not a
|
|
228
|
-
* library call: all credential/admin-target resolution lives in `@astrale-os/
|
|
229
|
-
* astrale` and we don't want it (or a kernel client) pulled into this build
|
|
230
|
-
* tool — we only own the fresh URL. A missing `astrale` is reported with the
|
|
231
|
-
* exact line to run by hand, so a deploy that already succeeded isn't a dead end.
|
|
232
|
-
*/
|
|
233
|
-
async function publishToAdmin(args: {
|
|
234
|
-
origin: string
|
|
235
|
-
name: string
|
|
236
|
-
url: string
|
|
237
|
-
description?: string
|
|
238
|
-
installByDefault?: boolean
|
|
239
|
-
}): Promise<number> {
|
|
240
|
-
// `--public-url`, not `--url`: the operator CLI reserves `--url` for kernel
|
|
241
|
-
// targeting, so the domain's public address rides its own flag.
|
|
242
|
-
const cliArgs = [
|
|
243
|
-
'domain',
|
|
244
|
-
'publish',
|
|
245
|
-
'--origin',
|
|
246
|
-
args.origin,
|
|
247
|
-
'--name',
|
|
248
|
-
args.name,
|
|
249
|
-
'--public-url',
|
|
250
|
-
args.url,
|
|
251
|
-
]
|
|
252
|
-
if (args.description) cliArgs.push('--description', args.description)
|
|
253
|
-
if (args.installByDefault) cliArgs.push('--install-by-default')
|
|
254
|
-
|
|
255
|
-
info(`publishing to the admin catalog → astrale ${cliArgs.join(' ')}`)
|
|
256
|
-
return await new Promise<number>((resolveCode) => {
|
|
257
|
-
const child = spawn('astrale', cliArgs, { stdio: 'inherit' })
|
|
258
|
-
child.on('error', (err) => {
|
|
259
|
-
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
260
|
-
error(
|
|
261
|
-
`\`astrale\` CLI not found on PATH. The deploy succeeded — publish it by hand:\n` +
|
|
262
|
-
` astrale ${cliArgs.join(' ')}`,
|
|
263
|
-
)
|
|
264
|
-
} else {
|
|
265
|
-
error(`failed to run \`astrale domain publish\`: ${err.message}`)
|
|
266
|
-
}
|
|
267
|
-
resolveCode(1)
|
|
268
|
-
})
|
|
269
|
-
child.on('exit', (code) => resolveCode(code ?? 1))
|
|
270
|
-
})
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** Best-effort read of the project's package.json `name`/`description` (publish defaults). */
|
|
274
|
-
function readPackageMeta(projectDir: string): { name?: string; description?: string } {
|
|
275
|
-
try {
|
|
276
|
-
const pkg = JSON.parse(readFileSync(join(projectDir, 'package.json'), 'utf8')) as {
|
|
277
|
-
name?: string
|
|
278
|
-
description?: string
|
|
279
|
-
}
|
|
280
|
-
return { name: pkg.name, description: pkg.description }
|
|
281
|
-
} catch {
|
|
282
|
-
return {}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
247
|
async function runWatch(args: {
|
|
287
248
|
adapter: DomainAdapter<unknown>
|
|
288
249
|
params: unknown
|
|
@@ -492,7 +453,7 @@ function loadSecrets(
|
|
|
492
453
|
const file = adapter.secretsFile?.(params)
|
|
493
454
|
if (!file) return {}
|
|
494
455
|
const abs = isAbsolute(file) ? file : join(projectDir, file)
|
|
495
|
-
return
|
|
456
|
+
return loadDeclaredSecrets(abs, file)
|
|
496
457
|
}
|
|
497
458
|
|
|
498
459
|
function findConfig(dir: string): string | undefined {
|
|
@@ -568,12 +529,19 @@ export function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
568
529
|
// catalog after a successful deploy. The `publish` command implies it.
|
|
569
530
|
const publish = rest.includes('--publish')
|
|
570
531
|
const installByDefault = rest.includes('--install-by-default')
|
|
532
|
+
// `--dry-run` (publish): pack + `npm publish --dry-run` and skip the catalog
|
|
533
|
+
// register. A boolean flag, filtered out of `positionals` below.
|
|
534
|
+
const dryRun = rest.includes('--dry-run')
|
|
535
|
+
// Publish modes: `--schema` = schema only · `--skip-schema` = catalog only.
|
|
536
|
+
const schemaOnly = rest.includes('--schema')
|
|
537
|
+
const skipSchema = rest.includes('--skip-schema')
|
|
571
538
|
// `--port <n>` / `--port=<n>`: consume the flag AND its value so neither
|
|
572
539
|
// leaks into the positionals (a bare `8788` would be read as the env name).
|
|
573
540
|
let port: number | undefined
|
|
574
541
|
let host: string | undefined
|
|
575
542
|
let name: string | undefined
|
|
576
543
|
let publicUrl: string | undefined
|
|
544
|
+
let otp: string | undefined
|
|
577
545
|
const cleaned: string[] = []
|
|
578
546
|
for (let i = 0; i < rest.length; i++) {
|
|
579
547
|
const a = rest[i]!
|
|
@@ -601,17 +569,38 @@ export function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
601
569
|
throw new Error('--public-url needs a value (the address the catalog points at)')
|
|
602
570
|
} else if (a.startsWith('--public-url=')) {
|
|
603
571
|
publicUrl = a.slice('--public-url='.length)
|
|
572
|
+
} else if (a === '--otp') {
|
|
573
|
+
otp = rest[++i]
|
|
574
|
+
if (!otp) throw new Error('--otp needs a value (your 2FA one-time password)')
|
|
575
|
+
} else if (a.startsWith('--otp=')) {
|
|
576
|
+
otp = a.slice('--otp='.length)
|
|
604
577
|
} else {
|
|
605
578
|
cleaned.push(a)
|
|
606
579
|
}
|
|
607
580
|
}
|
|
608
581
|
const positionals = cleaned.filter((a) => !a.startsWith('-'))
|
|
609
582
|
|
|
583
|
+
// `--dry-run` belongs ONLY to `publish` — never let it silently no-op a deploy.
|
|
584
|
+
if (dryRun && cmd !== 'publish') throw new Error('`--dry-run` is only valid for `publish`.')
|
|
585
|
+
// `--otp` feeds the schema publish (publish, or deploy/prod `--publish`).
|
|
586
|
+
if (otp !== undefined && !['publish', 'deploy', 'prod'].includes(cmd ?? ''))
|
|
587
|
+
throw new Error('`--otp` is only valid for the publish / deploy commands.')
|
|
588
|
+
// `--schema` (schema only) / `--skip-schema` (catalog only): publish-only, opposite.
|
|
589
|
+
if (schemaOnly && cmd !== 'publish') throw new Error('`--schema` is only valid for `publish`.')
|
|
590
|
+
if (schemaOnly && skipSchema)
|
|
591
|
+
throw new Error(
|
|
592
|
+
'`--schema` (schema only) and `--skip-schema` (catalog only) are mutually exclusive.',
|
|
593
|
+
)
|
|
594
|
+
|
|
610
595
|
// Publish-related fields shared by deploy/prod/publish (publish itself is
|
|
611
|
-
// gated per-command: opt-in via `--publish`, always-on for `publish`).
|
|
596
|
+
// gated per-command: opt-in via `--publish`, always-on for `publish`). `otp` /
|
|
597
|
+
// `skipSchema` ride along because the schema-publish step runs on the
|
|
598
|
+
// `publish` command AND the `deploy --publish` tail.
|
|
612
599
|
const pub = {
|
|
613
600
|
...(name !== undefined ? { name } : {}),
|
|
614
601
|
...(installByDefault ? { installByDefault } : {}),
|
|
602
|
+
...(otp !== undefined ? { otp } : {}),
|
|
603
|
+
...(skipSchema ? { skipSchema } : {}),
|
|
615
604
|
}
|
|
616
605
|
|
|
617
606
|
switch (cmd) {
|
|
@@ -631,15 +620,17 @@ export function parseArgs(argv: readonly string[]): ParsedArgs {
|
|
|
631
620
|
return { command: 'deploy', env, watch, ...(publish ? { publish } : {}), ...pub }
|
|
632
621
|
}
|
|
633
622
|
case 'publish':
|
|
634
|
-
//
|
|
635
|
-
//
|
|
636
|
-
//
|
|
623
|
+
// Release: publish ./schema + register the deployed URL (no deploy).
|
|
624
|
+
// `--schema` = schema only · `--skip-schema` = catalog only · `--dry-run`
|
|
625
|
+
// packs the schema and skips the catalog register.
|
|
637
626
|
return {
|
|
638
627
|
command: 'publish',
|
|
639
628
|
env: positionals[0] ?? 'prod',
|
|
640
629
|
watch: false,
|
|
641
630
|
...pub,
|
|
642
631
|
...(publicUrl !== undefined ? { publicUrl } : {}),
|
|
632
|
+
...(dryRun ? { dryRun } : {}),
|
|
633
|
+
...(schemaOnly ? { schemaOnly } : {}),
|
|
643
634
|
}
|
|
644
635
|
case 'build':
|
|
645
636
|
// Spec-only — no env resolution happens on this path.
|
|
@@ -655,23 +646,6 @@ function rel(from: string, to: string): string {
|
|
|
655
646
|
|
|
656
647
|
// ── output ───────────────────────────────────────────────────────────────
|
|
657
648
|
|
|
658
|
-
const GREEN = '\x1b[32m'
|
|
659
|
-
const DIM = '\x1b[2m'
|
|
660
|
-
const BOLD = '\x1b[1m'
|
|
661
|
-
const YELLOW = '\x1b[33m'
|
|
662
|
-
const RED = '\x1b[31m'
|
|
663
|
-
const RESET = '\x1b[0m'
|
|
664
|
-
|
|
665
|
-
function info(msg: string): void {
|
|
666
|
-
process.stdout.write(`${DIM}›${RESET} ${msg}\n`)
|
|
667
|
-
}
|
|
668
|
-
function warn(msg: string): void {
|
|
669
|
-
process.stderr.write(`${YELLOW}!${RESET} ${msg}\n`)
|
|
670
|
-
}
|
|
671
|
-
function error(msg: string): void {
|
|
672
|
-
process.stderr.write(`${RED}✗${RESET} ${msg}\n`)
|
|
673
|
-
}
|
|
674
|
-
|
|
675
649
|
function printDevReady(url: string, domain: DomainInfo): void {
|
|
676
650
|
process.stdout.write(
|
|
677
651
|
`\n${GREEN}✓${RESET} ${BOLD}${domain.origin}${RESET} running (dev, hot-reload)\n` +
|
|
@@ -707,13 +681,18 @@ function printUsage(msg?: string): void {
|
|
|
707
681
|
` astrale-domain dev # deploy dev --watch (hot-reload, prints URL)\n` +
|
|
708
682
|
` astrale-domain prod # deploy prod\n` +
|
|
709
683
|
` astrale-domain deploy <env> # deploy any env key (--watch optional)\n` +
|
|
710
|
-
` astrale-domain publish [env] #
|
|
684
|
+
` astrale-domain publish [env] # RELEASE: publish ./schema to npm (if any) + register the deployed URL\n` +
|
|
711
685
|
` astrale-domain build # rebuild the diagnostic spec only\n` +
|
|
712
686
|
`\nFlags:\n` +
|
|
713
|
-
` --publish # on deploy/prod: ALSO register the deployed URL
|
|
687
|
+
` --publish # on deploy/prod: ALSO release (publish ./schema + register the deployed URL)\n` +
|
|
688
|
+
` --schema # (publish) publish ONLY the ./schema package — no catalog, no config\n` +
|
|
689
|
+
` --skip-schema # (publish / deploy --publish) register the catalog only; don't publish ./schema\n` +
|
|
714
690
|
` --name <slug> # registry name to publish under (default: the origin's first label)\n` +
|
|
715
691
|
` --public-url <url> # (publish) address the catalog points at (default: https://<origin>)\n` +
|
|
716
692
|
` --install-by-default # mark the published domain for install on every new instance\n` +
|
|
717
|
-
|
|
693
|
+
` --dry-run # (publish) pack + npm publish --dry-run; never ships, skips catalog\n` +
|
|
694
|
+
` --otp <code> # (publish / deploy --publish) 2FA code for npm publish\n` +
|
|
695
|
+
`\n catalog register shells out to \`astrale domain publish\` (needs \`astrale\` on PATH).\n` +
|
|
696
|
+
` schema publish uses \`pnpm pack\` then \`npm publish\` (needs \`pnpm\` + \`npm\`; npm handles 2FA).\n\n`,
|
|
718
697
|
)
|
|
719
698
|
}
|
package/src/cli/spec.ts
CHANGED
|
@@ -33,6 +33,7 @@ export async function buildProjectSpec(
|
|
|
33
33
|
methods: def.methods,
|
|
34
34
|
...(def.views ? { views: def.views } : {}),
|
|
35
35
|
...(def.functions ? { remoteFunctions: def.functions } : {}),
|
|
36
|
+
...(def.path ? { path: def.path } : {}),
|
|
36
37
|
} as RemoteDomainConfig<Schema, unknown>
|
|
37
38
|
|
|
38
39
|
const domain = defineRemoteDomain<unknown>()(config)
|