@ossy/cli 1.16.11 → 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.
package/src/index.js CHANGED
@@ -2,13 +2,11 @@
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { fileURLToPath } from 'node:url'
5
- import { build } from '@ossy/app'
6
5
  import * as Auth from './auth/cli.js'
7
6
  import * as Call from './call/cli.js'
8
- import * as Cms from './cms/cli.js'
9
- import * as Init from './init/cli.js'
10
- import { publish } from './publish/cli.js'
7
+ import * as App from './app/cli.js'
11
8
  import * as Registry from './registry/cli.js'
9
+ import * as UploadDir from './upload-dir/cli.js'
12
10
  import * as Workspace from './workspace/cli.js'
13
11
 
14
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
@@ -25,20 +23,15 @@ Usage:
25
23
  Local utilities:
26
24
  auth <subcommand> Sign in / out and check status: login | logout | status
27
25
  workspace <subcommand> Manage active workspace: use | list | current
28
- init [dir] Scaffold a new Ossy app
29
- build Production build of the app in the current directory
30
- cms validate Validate src/config.js (workspaceId + resourceTemplates)
26
+ app <subcommand> App commands: init | build | upload | validate | publish
31
27
 
32
28
  Action dispatcher (any Ossy SDK action, no per-action wiring needed):
33
29
  call <action.id> Run \`ossy call --help\` for the full catalog
34
30
 
35
31
  Workflows (wrap one or more actions with extra ergonomics):
36
- cms upload Upload resource templates from src/config.js
37
- (wraps workspaces.import-resource-templates)
32
+ upload-dir <dir> <loc> Recursively upload a local directory to the CMS
38
33
  registry <subcommand> ecr-push-credentials (wraps registry.ecr-push-credentials,
39
34
  adds --format github-actions and password masking)
40
- publish Deploy via @ossy/deployment-tools, then upload
41
- resource templates and site build artifacts
42
35
 
43
36
  Options:
44
37
  -h, --help Show this help, or for a command: ossy <command> --help
@@ -62,48 +55,42 @@ Options (login):
62
55
  list List workspaces the current user can access
63
56
  current Print the active workspace id
64
57
  `,
65
- init: `ossy init [dir]
66
- Scaffold a new Ossy app in [dir] (defaults to current directory).
67
- Creates src/home.page.jsx, src/config.js, and package.json (if missing).
68
- `,
69
- build: `ossy build
70
- Run the production build for the app in the current directory.
71
- See the @ossy/app README for build options.
72
- `,
73
- publish: `ossy publish [options]
74
- Multi-step workflow: deploy via @ossy/deployment-tools (temporary), then
75
- optionally upload resource templates and site build artifacts. The deploy
76
- step is intended to go away once the platform reacts to artifact events;
77
- the CMS upload steps are equivalent to:
78
- workspaces.import-resource-templates (resource templates)
79
- /site-artifacts/presign-batch + /commit-batch (site artifacts)
80
-
81
- Options:
82
- -a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
83
- -d, --domain Site domain
84
- -p, --platform Target deployment platform
85
- -c, --config Path to src/config.js
86
- --platforms-path Path to platforms.json
87
- --deployments-path Glob for deployments JSON files
88
- --all Deploy all sites for the platform
89
- --skip-resource-templates Skip post-deploy resource-template upload
90
- --skip-site-artifacts Skip post-deploy site-artifact upload
91
- --site-artifacts-build-dir Override the site build directory
92
- --api-url API base URL for CMS calls
93
- `,
94
- cms: `ossy cms <subcommand>
58
+ app: `ossy app <subcommand>
95
59
  Subcommands:
60
+ init Scaffold a new Ossy app (default dir: current directory)
61
+ build Production build of the app in the current directory
96
62
  upload Upload resource templates from src/config.js to the workspace
97
63
  (wraps the SDK action workspaces.import-resource-templates)
98
64
  validate Validate ossy app config and resource templates locally
65
+ publish Upload resource templates and build/ to the CMS
66
+ (resource templates + build files to /sites/{domain})
99
67
 
100
68
  Options (upload):
101
69
  -a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
102
70
  -c, --config Path to src/config.js
103
- --api-url API base URL for CMS calls
71
+ --api-url API base URL for API calls
104
72
 
105
73
  Options (validate):
106
74
  -c, --config Path to src/config.js
75
+
76
+ Options (publish):
77
+ -a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
78
+ -c, --config Path to src/config.js
79
+ --build-dir Override build directory (default: <package>/build)
80
+ --build-dest Override remote CMS location (default: /sites/{domain})
81
+ --skip-resource-templates Skip resource template upload
82
+ --api-url API base URL for API calls
83
+ `,
84
+ 'upload-dir': `ossy upload-dir <localDir> <remoteLocation> [options]
85
+ Recursively mirror a local directory into the Ossy CMS.
86
+ Creates parent directories first, then uploads every file.
87
+ Per-item errors are logged and skipped; exit code is non-zero if any failed.
88
+
89
+ Options:
90
+ --dry-run Print what would happen without making any API calls
91
+ -a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
92
+ -w, --workspace-id Workspace id (or set OSSY_WORKSPACE_ID, or run \`ossy workspace use\`)
93
+ --api-url Override API base URL
107
94
  `,
108
95
  registry: `ossy registry <subcommand>
109
96
  Subcommands:
@@ -151,24 +138,16 @@ const run = async () => {
151
138
  await Call.handler(restArgs)
152
139
  return
153
140
  }
154
- if (command === 'registry') {
155
- await Registry.handler(restArgs)
156
- return
157
- }
158
- if (command === 'cms') {
159
- Cms.handler(restArgs)
141
+ if (command === 'upload-dir') {
142
+ await UploadDir.handler(restArgs)
160
143
  return
161
144
  }
162
- if (command === 'init') {
163
- Init.init(restArgs)
164
- return
165
- }
166
- if (command === 'publish') {
167
- await publish(restArgs)
145
+ if (command === 'registry') {
146
+ await Registry.handler(restArgs)
168
147
  return
169
148
  }
170
- if (command === 'build') {
171
- await build(restArgs)
149
+ if (command === 'app') {
150
+ await App.handler(restArgs)
172
151
  return
173
152
  }
174
153
  console.error(`[@ossy/cli] Unknown command: ${command}`)
@@ -1,6 +1,6 @@
1
1
  import arg from 'arg'
2
2
  import { appendFileSync } from 'fs'
3
- import { requireCmsAuthentication } from '../cms/upload-resource-templates.js'
3
+ import { requireAppAuthentication } from '../app/upload-resource-templates.js'
4
4
  import { getAuth, getWorkspaceId } from '../state.js'
5
5
 
6
6
  const DEFAULT_API_URL = 'https://api.ossy.se/api/v0'
@@ -25,7 +25,7 @@ export async function ecrPushCredentials (options) {
25
25
 
26
26
  let token
27
27
  try {
28
- token = requireCmsAuthentication(
28
+ token = requireAppAuthentication(
29
29
  getAuth({ flag: parsed['--authentication'] }),
30
30
  'registry ecr-push-credentials'
31
31
  )
@@ -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
+ }
@@ -1,181 +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
- import { getAuth } from '../state.js'
14
-
15
- const DEPLOYMENT_TOOLS = '@ossy/deployment-tools'
16
-
17
- function runNpxDeploymentTools (deploymentArgs) {
18
- const args = ['--yes', DEPLOYMENT_TOOLS, 'deployment', ...deploymentArgs]
19
- logInfo({ message: `[@ossy/cli] publish: npx ${DEPLOYMENT_TOOLS} deployment ${deploymentArgs[0]} …` })
20
- return new Promise((resolvePromise, reject) => {
21
- const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
22
- const child = spawn(npx, args, { stdio: 'inherit' })
23
- child.on('error', reject)
24
- child.on('close', (code) => {
25
- if (code === 0) resolvePromise()
26
- else reject(new Error(`npx exited with code ${code}`))
27
- })
28
- })
29
- }
30
-
31
- /**
32
- * Publish container deployments: default = one site (`deployment deploy`);
33
- * `--all` = `deployment deploy-all` for the platform.
34
- *
35
- * Requires **`--authentication`** (Ossy API JWT) or **`OSSY_API_KEY`**. Container
36
- * deployments target **ECR** (`registry` in deployments JSON); the worker pulls
37
- * with **IAM** (`aws ecr get-login-password`). No registry password is sent on the queue.
38
- */
39
- export async function publish (options) {
40
- const parsedArgs = arg({
41
- '--authentication': String,
42
- '-a': '--authentication',
43
- '--domain': String,
44
- '-d': '--domain',
45
- '--platform': String,
46
- '-p': '--platform',
47
- '--config': String,
48
- '-c': '--config',
49
- '--platforms-path': String,
50
- '-pp': '--platforms-path',
51
- '--deployments-path': String,
52
- '-dp': '--deployments-path',
53
- '--all': Boolean,
54
- '--skip-resource-templates': Boolean,
55
- '--skip-site-artifacts': Boolean,
56
- '--site-artifacts-build-dir': String,
57
- '--api-url': String,
58
- }, { argv: options })
59
-
60
- const platformsPath = parsedArgs['--platforms-path']
61
- const deploymentsPath = parsedArgs['--deployments-path']
62
-
63
- const apiUrlForTemplates = parsedArgs['--api-url']
64
-
65
- let apiToken
66
- try {
67
- apiToken = requireCmsAuthentication(
68
- getAuth({ flag: parsedArgs['--authentication'] }),
69
- 'publish'
70
- )
71
- } catch (e) {
72
- logError({
73
- message:
74
- e?.message
75
- || '[@ossy/cli] publish: pass --authentication (-a), set OSSY_API_KEY, or run `ossy auth login`.',
76
- })
77
- process.exit(1)
78
- }
79
-
80
- if (!platformsPath || !deploymentsPath) {
81
- logError({
82
- message: '[@ossy/cli] publish: --platforms-path (-pp) and --deployments-path (-dp) are required.'
83
- })
84
- process.exit(1)
85
- }
86
-
87
- const configFlag = parsedArgs['--config']
88
- let configPath = resolveAppConfigPath(configFlag)
89
- if (configFlag) {
90
- if (!configPath || !existsSync(configPath)) {
91
- logError({ message: `[@ossy/cli] publish: --config file not found: ${configFlag}` })
92
- process.exit(1)
93
- }
94
- }
95
-
96
- const skipResourceTemplates = parsedArgs['--skip-resource-templates']
97
- const skipSiteArtifacts = parsedArgs['--skip-site-artifacts']
98
- const siteArtifactsBuildDir = parsedArgs['--site-artifacts-build-dir']
99
-
100
- const fromConfig = configPath ? readWebsiteConfigDeployFields(configPath) : {}
101
-
102
- if (parsedArgs['--all']) {
103
- let targetPlatform = parsedArgs['--platform'] || fromConfig.platform
104
- if (!targetPlatform) {
105
- logError({
106
- message: '[@ossy/cli] publish --all: pass --platform (-p) or set platform in src/config.js (or --config).'
107
- })
108
- process.exit(1)
109
- }
110
- logInfo({ message: `[@ossy/cli] publish --all: platform=${targetPlatform}` })
111
- await runNpxDeploymentTools([
112
- 'deploy-all',
113
- '-p', targetPlatform,
114
- '--platforms-path', platformsPath,
115
- '--deployments-path', deploymentsPath,
116
- ])
117
- if (!skipResourceTemplates && configPath) {
118
- await maybeUploadResourceTemplatesAfterPublish({
119
- configPath,
120
- cmsToken: apiToken,
121
- apiUrlFlag: apiUrlForTemplates,
122
- })
123
- }
124
- if (!skipSiteArtifacts && configPath) {
125
- await maybeUploadSiteArtifactsAfterPublish({
126
- configPath,
127
- cmsToken: apiToken,
128
- apiUrlFlag: apiUrlForTemplates,
129
- buildDir: siteArtifactsBuildDir,
130
- })
131
- }
132
- return
133
- }
134
-
135
- let targetDomain = parsedArgs['--domain'] || fromConfig.domain
136
- let targetPlatform = parsedArgs['--platform'] || fromConfig.platform
137
-
138
- if (!targetPlatform && targetDomain && deploymentsPath) {
139
- const resolved = resolvePlatformFromDeployments(deploymentsPath, targetDomain)
140
- if (resolved && typeof resolved === 'object' && resolved.ambiguous) {
141
- logError({
142
- message: `[@ossy/cli] publish: domain "${targetDomain}" matches multiple platforms (${resolved.platforms.join(', ')}). Set platform in src/config.js or pass --platform (-p).`
143
- })
144
- process.exit(1)
145
- }
146
- targetPlatform = resolved
147
- }
148
-
149
- if (!targetDomain || !targetPlatform) {
150
- logError({
151
- 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.'
152
- })
153
- process.exit(1)
154
- }
155
-
156
- logInfo({ message: `[@ossy/cli] publish: domain=${targetDomain} platform=${targetPlatform}` })
157
-
158
- await runNpxDeploymentTools([
159
- 'deploy',
160
- '-d', targetDomain,
161
- '-p', targetPlatform,
162
- '--platforms-path', platformsPath,
163
- '--deployments-path', deploymentsPath,
164
- ])
165
-
166
- if (!skipResourceTemplates && configPath) {
167
- await maybeUploadResourceTemplatesAfterPublish({
168
- configPath,
169
- cmsToken: apiToken,
170
- apiUrlFlag: apiUrlForTemplates,
171
- })
172
- }
173
- if (!skipSiteArtifacts && configPath) {
174
- await maybeUploadSiteArtifactsAfterPublish({
175
- configPath,
176
- cmsToken: apiToken,
177
- apiUrlFlag: apiUrlForTemplates,
178
- buildDir: siteArtifactsBuildDir,
179
- })
180
- }
181
- }
@@ -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
- }