@ossy/cli 1.16.10 → 1.17.3

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.
@@ -0,0 +1,270 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import * as Sdk from '@ossy/sdk'
4
+ import { logInfo, logError } from '../log.js'
5
+ import { getAuth, getApiUrl, getWorkspaceId } from '../state.js'
6
+ import { prepareFileUpload, uploadFileToUrl } from '../file.js'
7
+
8
+ const HELP = `ossy upload-dir <localDir> <remoteLocation> [options]
9
+
10
+ Recursively mirror a local directory into the Ossy CMS.
11
+ Creates folders first (parents before children), then uploads every file.
12
+ Per-item errors are logged and skipped; exit code is non-zero if any failed.
13
+
14
+ Arguments:
15
+ <localDir> Path to the local directory to upload
16
+ <remoteLocation> Remote location to mirror into (e.g. /my-folder)
17
+
18
+ Options:
19
+ --dry-run Print what would happen without making any API calls
20
+ -a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
21
+ -w, --workspace-id Workspace id (or set OSSY_WORKSPACE_ID, or run \`ossy workspace use\`)
22
+ --api-url Override API base URL
23
+ -h, --help Show this help
24
+
25
+ Example:
26
+ ossy upload-dir ./public /my-folder
27
+ ossy upload-dir ./public /my-folder --dry-run
28
+ `
29
+
30
+ /**
31
+ * Walk a local directory tree.
32
+ * Returns dirs breadth-first (parents before children) and files in discovery order.
33
+ * @param {string} absRoot
34
+ * @returns {{ dirs: string[], files: string[] }}
35
+ */
36
+ export function walk (absRoot) {
37
+ const dirs = []
38
+ const files = []
39
+
40
+ function recurse (dir) {
41
+ let entries
42
+ try {
43
+ entries = fs.readdirSync(dir, { withFileTypes: true })
44
+ } catch (err) {
45
+ throw new Error(`Cannot read directory ${dir}: ${err.message}`)
46
+ }
47
+ const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name))
48
+ const subdirs = []
49
+ for (const entry of sorted) {
50
+ const full = path.join(dir, entry.name)
51
+ if (entry.isDirectory()) {
52
+ dirs.push(full)
53
+ subdirs.push(full)
54
+ } else if (entry.isFile()) {
55
+ files.push(full)
56
+ }
57
+ }
58
+ for (const sub of subdirs) recurse(sub)
59
+ }
60
+
61
+ recurse(absRoot)
62
+ return { dirs, files }
63
+ }
64
+
65
+ /**
66
+ * Parse argv for the upload-dir command.
67
+ * @param {string[]} argv
68
+ * @returns {{ localDir: string|null, remoteLocation: string|null, dryRun: boolean, authentication: string|null, workspaceId: string|null, apiUrl: string|null, help: boolean }}
69
+ */
70
+ export function parseArgs (argv) {
71
+ const out = {
72
+ localDir: null,
73
+ remoteLocation: null,
74
+ dryRun: false,
75
+ authentication: null,
76
+ workspaceId: null,
77
+ apiUrl: null,
78
+ help: false,
79
+ }
80
+
81
+ const NEEDS_VALUE = new Set(['--authentication', '-a', '--workspace-id', '-w', '--api-url'])
82
+
83
+ let i = 0
84
+ while (i < argv.length) {
85
+ const arg = argv[i]
86
+
87
+ if (arg === '--help' || arg === '-h') { out.help = true; i++; continue }
88
+ if (arg === '--dry-run') { out.dryRun = true; i++; continue }
89
+
90
+ if (arg.startsWith('--') && arg.includes('=')) {
91
+ const eq = arg.indexOf('=')
92
+ const key = arg.slice(2, eq)
93
+ const val = arg.slice(eq + 1)
94
+ if (key === 'authentication') out.authentication = val
95
+ else if (key === 'workspace-id') out.workspaceId = val
96
+ else if (key === 'api-url') out.apiUrl = val
97
+ i++; continue
98
+ }
99
+
100
+ if (NEEDS_VALUE.has(arg)) {
101
+ const val = argv[i + 1]
102
+ if (arg === '--authentication' || arg === '-a') out.authentication = val
103
+ else if (arg === '--workspace-id' || arg === '-w') out.workspaceId = val
104
+ else if (arg === '--api-url') out.apiUrl = val
105
+ i += 2; continue
106
+ }
107
+
108
+ if (!arg.startsWith('-')) {
109
+ if (out.localDir === null) { out.localDir = arg; i++; continue }
110
+ if (out.remoteLocation === null) { out.remoteLocation = arg; i++; continue }
111
+ }
112
+
113
+ i++
114
+ }
115
+
116
+ return out
117
+ }
118
+
119
+ /**
120
+ * Normalise a remote location path: leading slash, no trailing slash.
121
+ * @param {string} loc
122
+ * @returns {string}
123
+ */
124
+ function normaliseLoc (loc) {
125
+ let s = loc.replace(/\/+$/, '')
126
+ if (!s.startsWith('/')) s = '/' + s
127
+ return s
128
+ }
129
+
130
+ export async function handler (args) {
131
+ const parsed = parseArgs(args)
132
+
133
+ if (parsed.help) {
134
+ console.log(HELP)
135
+ return
136
+ }
137
+
138
+ if (!parsed.localDir || !parsed.remoteLocation) {
139
+ logError({ message: '[@ossy/cli] upload-dir: provide <localDir> and <remoteLocation>. Run `ossy upload-dir --help`.' })
140
+ process.exit(1)
141
+ }
142
+
143
+ const absRoot = path.resolve(parsed.localDir)
144
+ if (!fs.existsSync(absRoot) || !fs.statSync(absRoot).isDirectory()) {
145
+ logError({ message: `[@ossy/cli] upload-dir: not a directory: ${parsed.localDir}` })
146
+ process.exit(1)
147
+ }
148
+
149
+ const remoteBase = normaliseLoc(parsed.remoteLocation)
150
+
151
+ let dirs, files
152
+ try {
153
+ ;({ dirs, files } = walk(absRoot))
154
+ } catch (err) {
155
+ logError({ message: `[@ossy/cli] upload-dir: ${err.message}` })
156
+ process.exit(1)
157
+ }
158
+
159
+ const dryRun = parsed.dryRun
160
+ if (dryRun) {
161
+ console.log(`[dry-run] upload-dir ${absRoot} → ${remoteBase}`)
162
+ console.log(`[dry-run] ${dirs.length} director${dirs.length === 1 ? 'y' : 'ies'}, ${files.length} file${files.length === 1 ? '' : 's'}`)
163
+ for (const d of dirs) {
164
+ const rel = d.slice(absRoot.length)
165
+ const parent = normaliseLoc(remoteBase + path.dirname(rel))
166
+ const name = path.basename(d)
167
+ console.log(`[dry-run] mkdir ${parent}/${name}`)
168
+ }
169
+ for (const f of files) {
170
+ const rel = f.slice(absRoot.length)
171
+ const remoteDir = normaliseLoc(remoteBase + path.dirname(rel))
172
+ console.log(`[dry-run] upload ${f} → ${remoteDir}`)
173
+ }
174
+ return
175
+ }
176
+
177
+ const auth = getAuth({ flag: parsed.authentication })
178
+ const apiUrl = getApiUrl({ flag: parsed.apiUrl })
179
+ const workspaceId = getWorkspaceId({ flag: parsed.workspaceId })
180
+
181
+ const sdk = Sdk.SDK.of({
182
+ apiUrl,
183
+ workspaceId: workspaceId || undefined,
184
+ authorization: auth || undefined,
185
+ })
186
+
187
+ const createDir = Sdk.ResourcesCreateDirectory
188
+ const uploadAction = Sdk.ResourcesUpload
189
+
190
+ let dirsMade = 0
191
+ let filesUploaded = 0
192
+ let errors = 0
193
+
194
+ // Create directories (breadth-first so parents always exist first)
195
+ for (const d of dirs) {
196
+ const rel = d.slice(absRoot.length)
197
+ const parent = normaliseLoc(remoteBase + path.dirname(rel))
198
+ const name = path.basename(d)
199
+ logInfo({ message: `[@ossy/cli] upload-dir: mkdir ${parent}/${name}` })
200
+ try {
201
+ await sdk.makeRequest(createDir)({ location: parent, name })
202
+ dirsMade++
203
+ } catch (err) {
204
+ // Treat "already exists" (HTTP 409 / 422 / resource conflict) as success
205
+ const status = err && err.status
206
+ if (status === 409 || status === 422) {
207
+ logInfo({ message: `[@ossy/cli] upload-dir: dir already exists, skipping: ${parent}/${name}` })
208
+ dirsMade++
209
+ } else {
210
+ const msg = err && err.message ? err.message : String(err)
211
+ logError({ message: `[@ossy/cli] upload-dir: mkdir failed for ${parent}/${name}: ${msg}` })
212
+ errors++
213
+ }
214
+ }
215
+ }
216
+
217
+ // Upload files
218
+ for (const f of files) {
219
+ const rel = f.slice(absRoot.length)
220
+ const remoteDir = normaliseLoc(remoteBase + path.dirname(rel))
221
+ logInfo({ message: `[@ossy/cli] upload-dir: upload ${f} → ${remoteDir}` })
222
+
223
+ let fileMeta
224
+ try {
225
+ fileMeta = prepareFileUpload(f)
226
+ } catch (err) {
227
+ logError({ message: `[@ossy/cli] upload-dir: cannot read file ${f}: ${err.message}` })
228
+ errors++
229
+ continue
230
+ }
231
+
232
+ let result
233
+ try {
234
+ result = await sdk.makeRequest(uploadAction)({
235
+ location: remoteDir,
236
+ name: fileMeta.name,
237
+ type: fileMeta.type,
238
+ size: fileMeta.size,
239
+ })
240
+ } catch (err) {
241
+ const msg = err && err.message ? err.message : String(err)
242
+ logError({ message: `[@ossy/cli] upload-dir: upload failed for ${f}: ${msg}` })
243
+ errors++
244
+ continue
245
+ }
246
+
247
+ const uploadUrl = result && result.content && result.content.uploadUrl
248
+ if (!uploadUrl) {
249
+ logError({ message: `[@ossy/cli] upload-dir: no uploadUrl in response for ${f}` })
250
+ errors++
251
+ continue
252
+ }
253
+
254
+ try {
255
+ await uploadFileToUrl(fileMeta, uploadUrl)
256
+ filesUploaded++
257
+ } catch (err) {
258
+ logError({ message: `[@ossy/cli] upload-dir: S3 PUT failed for ${f}: ${err.message}` })
259
+ errors++
260
+ }
261
+ }
262
+
263
+ const summary = `${dirsMade} director${dirsMade === 1 ? 'y' : 'ies'} created, ${filesUploaded} file${filesUploaded === 1 ? '' : 's'} uploaded`
264
+ if (errors > 0) {
265
+ logError({ message: `[@ossy/cli] upload-dir: done with ${errors} error${errors === 1 ? '' : 's'} — ${summary}` })
266
+ process.exit(1)
267
+ } else {
268
+ logInfo({ message: `[@ossy/cli] upload-dir: done — ${summary}` })
269
+ }
270
+ }
@@ -0,0 +1,84 @@
1
+ import { logInfo, logError } from '../log.js'
2
+ import {
3
+ readConfig,
4
+ writeConfig,
5
+ getAuth,
6
+ getApiUrl,
7
+ getWorkspaceId,
8
+ } from '../state.js'
9
+
10
+ function ensureAuth () {
11
+ const auth = getAuth()
12
+ if (!auth) {
13
+ logError({
14
+ message: '[@ossy/cli] Not logged in. Run `ossy auth login` first.',
15
+ })
16
+ process.exit(1)
17
+ }
18
+ return auth
19
+ }
20
+
21
+ function use (options) {
22
+ const id = options[0]
23
+ if (!id) {
24
+ logError({ message: '[@ossy/cli] workspace use: pass a workspace id.' })
25
+ process.exit(1)
26
+ }
27
+ writeConfig({ ...(readConfig() || {}), workspaceId: id })
28
+ logInfo({ message: `[@ossy/cli] Active workspace: ${id}` })
29
+ }
30
+
31
+ async function list () {
32
+ const auth = ensureAuth()
33
+ const apiUrl = getApiUrl()
34
+
35
+ let res
36
+ try {
37
+ res = await fetch(`${apiUrl}/workspaces`, {
38
+ headers: { Authorization: auth, 'Content-Type': 'application/json' },
39
+ })
40
+ } catch (error) {
41
+ logError({ message: `[@ossy/cli] workspace list: could not reach ${apiUrl}`, error })
42
+ process.exit(1)
43
+ }
44
+
45
+ if (!res.ok) {
46
+ logError({
47
+ message: `[@ossy/cli] workspace list: HTTP ${res.status} from ${apiUrl}/workspaces`,
48
+ })
49
+ process.exit(1)
50
+ }
51
+
52
+ const workspaces = await res.json().catch(() => null)
53
+ if (!Array.isArray(workspaces) || workspaces.length === 0) {
54
+ console.log('No workspaces found.')
55
+ return
56
+ }
57
+
58
+ const active = getWorkspaceId()
59
+ for (const w of workspaces) {
60
+ const marker = w.id === active ? '* ' : ' '
61
+ console.log(`${marker}${w.id}\t${w.name || ''}`)
62
+ }
63
+ }
64
+
65
+ function current () {
66
+ const id = getWorkspaceId()
67
+ if (!id) {
68
+ console.log('(no active workspace)')
69
+ return
70
+ }
71
+ console.log(id)
72
+ }
73
+
74
+ export async function handler (args) {
75
+ const [sub, ...rest] = args
76
+ if (sub === 'use') return use(rest)
77
+ if (sub === 'list') return await list()
78
+ if (sub === 'current') return current()
79
+ logError({
80
+ message:
81
+ '[@ossy/cli] workspace: unknown subcommand. Use: ossy workspace use <id> | list | current',
82
+ })
83
+ process.exit(1)
84
+ }
@@ -1,180 +0,0 @@
1
- import { existsSync } from 'fs'
2
- import { spawn } from 'child_process'
3
- import arg from 'arg'
4
- import { logInfo, logError } from '../log.js'
5
- import { resolveAppConfigPath } from '../resolve-app-config-path.js'
6
- import {
7
- readWebsiteConfigDeployFields,
8
- resolvePlatformFromDeployments
9
- } from './resolve-config.js'
10
- import { maybeUploadResourceTemplatesAfterPublish } from './resource-templates-after-publish.js'
11
- import { maybeUploadSiteArtifactsAfterPublish } from './site-artifacts-after-publish.js'
12
- import { requireCmsAuthentication } from '../cms/upload-resource-templates.js'
13
-
14
- const DEPLOYMENT_TOOLS = '@ossy/deployment-tools'
15
-
16
- function runNpxDeploymentTools (deploymentArgs) {
17
- const args = ['--yes', DEPLOYMENT_TOOLS, 'deployment', ...deploymentArgs]
18
- logInfo({ message: `[@ossy/cli] publish: npx ${DEPLOYMENT_TOOLS} deployment ${deploymentArgs[0]} …` })
19
- return new Promise((resolvePromise, reject) => {
20
- const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
21
- const child = spawn(npx, args, { stdio: 'inherit' })
22
- child.on('error', reject)
23
- child.on('close', (code) => {
24
- if (code === 0) resolvePromise()
25
- else reject(new Error(`npx exited with code ${code}`))
26
- })
27
- })
28
- }
29
-
30
- /**
31
- * Publish container deployments: default = one site (`deployment deploy`);
32
- * `--all` = `deployment deploy-all` for the platform.
33
- *
34
- * Requires **`--authentication`** (Ossy API JWT) or **`OSSY_API_KEY`**. Container
35
- * deployments target **ECR** (`registry` in deployments JSON); the worker pulls
36
- * with **IAM** (`aws ecr get-login-password`). No registry password is sent on the queue.
37
- */
38
- export async function publish (options) {
39
- const parsedArgs = arg({
40
- '--authentication': String,
41
- '-a': '--authentication',
42
- '--domain': String,
43
- '-d': '--domain',
44
- '--platform': String,
45
- '-p': '--platform',
46
- '--config': String,
47
- '-c': '--config',
48
- '--platforms-path': String,
49
- '-pp': '--platforms-path',
50
- '--deployments-path': String,
51
- '-dp': '--deployments-path',
52
- '--all': Boolean,
53
- '--skip-resource-templates': Boolean,
54
- '--skip-site-artifacts': Boolean,
55
- '--site-artifacts-build-dir': String,
56
- '--api-url': String,
57
- }, { argv: options })
58
-
59
- const platformsPath = parsedArgs['--platforms-path']
60
- const deploymentsPath = parsedArgs['--deployments-path']
61
-
62
- const apiUrlForTemplates = parsedArgs['--api-url']
63
-
64
- let apiToken
65
- try {
66
- apiToken = requireCmsAuthentication(
67
- parsedArgs['--authentication'] || process.env.OSSY_API_KEY,
68
- 'publish'
69
- )
70
- } catch (e) {
71
- logError({
72
- message:
73
- e?.message
74
- || '[@ossy/cli] publish: pass --authentication (-a) or set OSSY_API_KEY (Ossy API JWT).',
75
- })
76
- process.exit(1)
77
- }
78
-
79
- if (!platformsPath || !deploymentsPath) {
80
- logError({
81
- message: '[@ossy/cli] publish: --platforms-path (-pp) and --deployments-path (-dp) are required.'
82
- })
83
- process.exit(1)
84
- }
85
-
86
- const configFlag = parsedArgs['--config']
87
- let configPath = resolveAppConfigPath(configFlag)
88
- if (configFlag) {
89
- if (!configPath || !existsSync(configPath)) {
90
- logError({ message: `[@ossy/cli] publish: --config file not found: ${configFlag}` })
91
- process.exit(1)
92
- }
93
- }
94
-
95
- const skipResourceTemplates = parsedArgs['--skip-resource-templates']
96
- const skipSiteArtifacts = parsedArgs['--skip-site-artifacts']
97
- const siteArtifactsBuildDir = parsedArgs['--site-artifacts-build-dir']
98
-
99
- const fromConfig = configPath ? readWebsiteConfigDeployFields(configPath) : {}
100
-
101
- if (parsedArgs['--all']) {
102
- let targetPlatform = parsedArgs['--platform'] || fromConfig.platform
103
- if (!targetPlatform) {
104
- logError({
105
- message: '[@ossy/cli] publish --all: pass --platform (-p) or set platform in src/config.js (or --config).'
106
- })
107
- process.exit(1)
108
- }
109
- logInfo({ message: `[@ossy/cli] publish --all: platform=${targetPlatform}` })
110
- await runNpxDeploymentTools([
111
- 'deploy-all',
112
- '-p', targetPlatform,
113
- '--platforms-path', platformsPath,
114
- '--deployments-path', deploymentsPath,
115
- ])
116
- if (!skipResourceTemplates && configPath) {
117
- await maybeUploadResourceTemplatesAfterPublish({
118
- configPath,
119
- cmsToken: apiToken,
120
- apiUrlFlag: apiUrlForTemplates,
121
- })
122
- }
123
- if (!skipSiteArtifacts && configPath) {
124
- await maybeUploadSiteArtifactsAfterPublish({
125
- configPath,
126
- cmsToken: apiToken,
127
- apiUrlFlag: apiUrlForTemplates,
128
- buildDir: siteArtifactsBuildDir,
129
- })
130
- }
131
- return
132
- }
133
-
134
- let targetDomain = parsedArgs['--domain'] || fromConfig.domain
135
- let targetPlatform = parsedArgs['--platform'] || fromConfig.platform
136
-
137
- if (!targetPlatform && targetDomain && deploymentsPath) {
138
- const resolved = resolvePlatformFromDeployments(deploymentsPath, targetDomain)
139
- if (resolved && typeof resolved === 'object' && resolved.ambiguous) {
140
- logError({
141
- message: `[@ossy/cli] publish: domain "${targetDomain}" matches multiple platforms (${resolved.platforms.join(', ')}). Set platform in src/config.js or pass --platform (-p).`
142
- })
143
- process.exit(1)
144
- }
145
- targetPlatform = resolved
146
- }
147
-
148
- if (!targetDomain || !targetPlatform) {
149
- logError({
150
- message: '[@ossy/cli] publish: need --domain (-d) and --platform (-p), or src/config.js / --config with domain (and optional platform); platform can be inferred from deployments when domain is unique.'
151
- })
152
- process.exit(1)
153
- }
154
-
155
- logInfo({ message: `[@ossy/cli] publish: domain=${targetDomain} platform=${targetPlatform}` })
156
-
157
- await runNpxDeploymentTools([
158
- 'deploy',
159
- '-d', targetDomain,
160
- '-p', targetPlatform,
161
- '--platforms-path', platformsPath,
162
- '--deployments-path', deploymentsPath,
163
- ])
164
-
165
- if (!skipResourceTemplates && configPath) {
166
- await maybeUploadResourceTemplatesAfterPublish({
167
- configPath,
168
- cmsToken: apiToken,
169
- apiUrlFlag: apiUrlForTemplates,
170
- })
171
- }
172
- if (!skipSiteArtifacts && configPath) {
173
- await maybeUploadSiteArtifactsAfterPublish({
174
- configPath,
175
- cmsToken: apiToken,
176
- apiUrlFlag: apiUrlForTemplates,
177
- buildDir: siteArtifactsBuildDir,
178
- })
179
- }
180
- }
@@ -1,44 +0,0 @@
1
- import { readFileSync } from 'fs'
2
- import { resolve } from 'path'
3
- import { globSync } from 'glob'
4
-
5
- /**
6
- * Reads string `domain` / `platform` from a site `src/config.js` without executing it
7
- * (config is often ESM with package-only imports).
8
- *
9
- * @param {string} configPath
10
- * @returns {{ domain?: string, platform?: string }}
11
- */
12
- export function readWebsiteConfigDeployFields (configPath) {
13
- const abs = resolve(configPath)
14
- const source = readFileSync(abs, 'utf8')
15
-
16
- const pick = (key) => {
17
- const m = source.match(new RegExp(`${key}\\s*:\\s*['"]([^'"]+)['"]`))
18
- return m ? m[1] : undefined
19
- }
20
-
21
- const domain = pick('domain')
22
- const platform = pick('platform') || pick('targetDeploymentPlatform')
23
-
24
- return { domain, platform }
25
- }
26
-
27
- /**
28
- * @param {string} deploymentsGlob
29
- * @param {string} domain
30
- * @returns {string | undefined | { ambiguous: true, platforms: string[] }}
31
- */
32
- export function resolvePlatformFromDeployments (deploymentsGlob, domain) {
33
- const filePaths = globSync(deploymentsGlob, { ignore: 'node_modules/**' })
34
- const deployments = filePaths.flatMap((p) => JSON.parse(readFileSync(p, 'utf8')))
35
- const matches = deployments.filter((d) => d.domain === domain)
36
- if (matches.length === 0) {
37
- return undefined
38
- }
39
- const platforms = [...new Set(matches.map((d) => d.targetDeploymentPlatform))]
40
- if (platforms.length > 1) {
41
- return { ambiguous: true, platforms }
42
- }
43
- return platforms[0]
44
- }
@@ -1,75 +0,0 @@
1
- import { logInfo } from '../log.js'
2
- import { readPublishFieldsFromWebsiteConfig } from './load-website-config.js'
3
- import {
4
- postResourceTemplates,
5
- requireCmsAuthentication,
6
- resolveApiBaseUrlForUpload,
7
- } from '../cms/upload-resource-templates.js'
8
-
9
- /**
10
- * After a successful deployment, sync `resourceTemplates` from app config to the workspace API.
11
- */
12
- export async function maybeUploadResourceTemplatesAfterPublish ({
13
- configPath,
14
- cmsToken,
15
- apiUrlFlag,
16
- }) {
17
- const config = readPublishFieldsFromWebsiteConfig(configPath)
18
- const workspaceId = config.workspaceId
19
- const resourceTemplates = config.resourceTemplates
20
-
21
- if (!workspaceId) {
22
- logInfo({
23
- message:
24
- '[@ossy/cli] publish: skipping resource template upload (no workspaceId in config)',
25
- })
26
- return
27
- }
28
- if (!Array.isArray(resourceTemplates) || resourceTemplates.length === 0) {
29
- logInfo({
30
- message:
31
- '[@ossy/cli] publish: skipping resource template upload (resourceTemplates missing or empty)',
32
- })
33
- return
34
- }
35
-
36
- const apiBaseUrl = resolveApiBaseUrlForUpload({
37
- flag: apiUrlFlag,
38
- envVar: process.env.OSSY_API_URL,
39
- configApiUrl: config?.apiUrl,
40
- })
41
- const uploadUrl = `${apiBaseUrl.replace(/\/$/, '')}/resource-templates`
42
- const authToken = requireCmsAuthentication(
43
- cmsToken,
44
- 'Resource template upload'
45
- )
46
-
47
- logInfo({
48
- message: `[@ossy/cli] publish: uploading resource templates → POST ${uploadUrl}`,
49
- })
50
-
51
- const response = await postResourceTemplates({
52
- apiBaseUrl,
53
- token: authToken,
54
- workspaceId,
55
- resourceTemplates,
56
- })
57
-
58
- if (!response.ok) {
59
- const text = await response.text().catch(() => '')
60
- const hint404 =
61
- response.status === 404
62
- ? ` Wrong host or path: requested ${uploadUrl}. Confirm OSSY_API_URL / --api-url points at the Ossy API base including /api/v0.`
63
- : ''
64
- const hint401 =
65
- response.status === 401
66
- ? ' Use an Ossy API JWT with --authentication (or OSSY_API_KEY in CI), not a non-API deploy token.'
67
- : ''
68
- throw new Error(
69
- `Resource template upload failed: HTTP ${response.status}${hint404}${hint401}${
70
- text ? ` — ${text.slice(0, 200)}` : ''
71
- }`
72
- )
73
- }
74
- logInfo({ message: '[@ossy/cli] publish: resource templates uploaded' })
75
- }