@ossy/cli 0.16.13 → 1.0.1

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/README.md CHANGED
@@ -20,7 +20,7 @@ npx @ossy/cli dev
20
20
  npx @ossy/cli build
21
21
  ```
22
22
 
23
- Options: `--pages`, `--config`, `--destination`. See `@ossy/app` for details.
23
+ Options: e.g. `--config` for `src/config.js`. See `packages/app/README.md` for app build behavior.
24
24
 
25
25
  ## Publish (container / website)
26
26
 
@@ -43,23 +43,22 @@ The explicit **`deployment deploy`** step (SQS / deployment queue from **`@ossy/
43
43
 
44
44
  ---
45
45
 
46
- Details:
46
+ ### Authentication
47
+
48
+ **`publish`** requires **`--authentication` / `-a`** or **`OSSY_API_KEY`**: the **Ossy API JWT** (workspace API token). That same value is used for post-deploy CMS calls (resource templates, site artifacts).
49
+
50
+ Container deploys assume **Amazon ECR** in **`deployments.json`** (`registry` like `123456789012.dkr.ecr.eu-north-1.amazonaws.com`, **`image`** unchanged). The **worker** always pulls with **IAM** (`aws ecr get-login-password`); nothing in the queue carries a registry password. For **`docker push` from CI**, call **`POST /api/v0/registry/ecr/push-credentials`** with the same JWT and **`workspaceId`**, then **`docker login`** / **`docker push`**.
47
51
 
48
52
  ```bash
49
53
  cd packages/my-website
54
+ export OSSY_API_KEY=<ossy-api-jwt> # or pass --authentication <ossy-api-jwt>
55
+
50
56
  npx @ossy/cli publish \
51
- --username <github-username> \
52
- --authentication <github-or-deploy-token> \
53
- --cms-authentication <ossy-api-jwt> \
54
57
  --platforms-path ../infrastructure/platforms.json \
55
- --deployments-path ../infrastructure/deployments.json
58
+ --deployments-path "../infrastructure/deployments/**/*.json"
56
59
  ```
57
60
 
58
- - **`--cms-authentication`** — Ossy **API JWT** (from the app’s API tokens) for `POST /resource-templates` and site-artifacts. If omitted, **`--authentication`** is used for CMS calls too (only works when that value is already a valid Ossy JWT). In GitHub Actions, set repo secret **`OSSY_API_KEY`** and pass **`--cms-authentication`** explicitly; the deploy token is usually **not** valid for the API.
59
-
60
- ### Unifying deploy and CMS authentication (future)
61
-
62
- **`--authentication`** and **`--cms-authentication`** exist because **`deployment deploy`** (via **`@ossy/deployment-tools`**) and the **Ossy HTTP API** currently expect **different** credentials (e.g. GitHub / queue token vs Ossy-signed JWT). They could be merged into **one** flag (or a single env secret in CI) if either: (1) deploy is **no longer** invoked from the CLI and **`publish`** is only CMS/API work, or (2) the deploy path is changed to accept the **same** Ossy-issued token the API uses. That lines up with [**Future direction**](#future-direction-planned) (platform-driven rollouts instead of queue + dual tokens).
61
+ **Non-ECR container registries** are not supported; container rows in **`deployments.json`** must use **ECR** and a **`registry`** endpoint as described above.
63
62
 
64
63
  API and worker packages can use the same pattern: a minimal **`src/config.js`** with string-literal **`domain`** (and optional **`platform`**) matching **`deployments.json`**, then run **`publish`** from **`packages/api`** or **`packages/worker`** with **`--skip-resource-templates`** and **`--skip-site-artifacts`** (no website `workspaceId` / `build/` flow).
65
64
 
@@ -77,9 +76,12 @@ Requires network access so `npx` can run `@ossy/deployment-tools`.
77
76
 
78
77
  Upload resource templates to your workspace so they can be used in the UI.
79
78
 
79
+ Prefer **`--authentication` / `-a`** for the **Ossy API JWT** (same as **`publish`**); it matches **`OSSY_API_KEY`** in CI.
80
+
80
81
  ```bash
81
- npx @ossy/cli cms upload --authentication <cms-api-token> --config src/config.js
82
+ npx @ossy/cli cms upload --authentication <ossy-api-jwt> --config src/config.js
82
83
  # optional: --api-url https://api.ossy.se/api/v0 (or set OSSY_API_URL)
84
+ # In CI you may omit --authentication when OSSY_API_KEY is set
83
85
  ```
84
86
 
85
87
  When **`--config`** is omitted, **`./src/config.js`** is used if it exists (same as **`publish`**).
@@ -128,7 +130,7 @@ When **`--config`** is omitted, **`./src/config.js`** is used if it exists.
128
130
 
129
131
  | Argument | Description | Required |
130
132
  |----------|-------------|----------|
131
- | --authentication, -a | Your CMS API token | Yes (upload only) |
133
+ | --authentication, -a | **Ossy API JWT** (primary; same role as **`OSSY_API_KEY`**) | Yes (upload only) |
132
134
  | --config, -c | App config (`workspaceId`, `resourceTemplates`, …) | Optional if `./src/config.js` exists |
133
135
  | --api-url | API base URL for upload (`…/api/v0`) | No |
134
136
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ossy/cli",
3
- "version": "0.16.13",
3
+ "version": "1.0.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ossy-se/packages.git"
@@ -18,7 +18,7 @@
18
18
  "bin": "./src/index.js",
19
19
  "dependencies": {
20
20
  "@babel/parser": "^7.28.6",
21
- "@ossy/app": "^0.15.13",
21
+ "@ossy/app": "^1.0.1",
22
22
  "arg": "^5.0.2",
23
23
  "glob": "^10.3.10"
24
24
  },
@@ -30,5 +30,5 @@
30
30
  "/src",
31
31
  "README.md"
32
32
  ],
33
- "gitHead": "280ce1f62d7daf721909ac5eabe4181991e00456"
33
+ "gitHead": "e5276fdb5dedf68b966d851077461b188c613241"
34
34
  }
package/src/cms/cli.js CHANGED
@@ -22,11 +22,15 @@ const upload = (options) => {
22
22
  '--api-url': String,
23
23
  }, { argv: options })
24
24
 
25
- const token = parsedArgs['--authentication']
25
+ const token =
26
+ parsedArgs['--authentication'] || process.env.OSSY_API_KEY
26
27
  const apiUrlFlag = parsedArgs['--api-url']
27
28
 
28
29
  if (!token) {
29
- logError({ message: '[@ossy/cli] No token provided. Use --authentication or -a' })
30
+ logError({
31
+ message:
32
+ '[@ossy/cli] No token provided. Use --authentication / -a or set OSSY_API_KEY',
33
+ })
30
34
  process.exit(1)
31
35
  }
32
36
 
@@ -19,8 +19,8 @@ export function requireCmsAuthentication (token, label) {
19
19
  if (!normalized) {
20
20
  throw new Error(
21
21
  `[@ossy/cli] publish: ${label} needs a non-empty Ossy API JWT. ` +
22
- 'Pass --cms-authentication with a token from the Ossy app (API tokens), or set the OSSY_API_KEY secret in CI. ' +
23
- 'The GitHub token used for --authentication (container deploy) is not accepted by the API.'
22
+ 'Pass --authentication with a token from the Ossy app (API tokens), or set OSSY_API_KEY in CI. ' +
23
+ 'Non-API deploy tokens are not accepted by the Ossy API.'
24
24
  )
25
25
  }
26
26
  return normalized
package/src/index.js CHANGED
@@ -3,17 +3,22 @@ import { build, dev } from '@ossy/app'
3
3
  import * as Cms from './cms/cli.js'
4
4
  import * as Init from './init/cli.js'
5
5
  import { publish } from './publish/cli.js'
6
+ import * as Registry from './registry/cli.js'
6
7
 
7
8
  const [,, command, ...restArgs] = process.argv
8
9
 
9
10
  if (!command) {
10
11
  console.error(
11
- '[@ossy/cli] No command provided. Usage: ossy dev | build [--worker] | publish | init | cms <subcommand>'
12
+ '[@ossy/cli] No command provided. Usage: ossy dev | build [--worker] | publish | registry <subcommand> | init | cms <subcommand>'
12
13
  )
13
14
  process.exit(1)
14
15
  }
15
16
 
16
17
  const run = async () => {
18
+ if (command === 'registry') {
19
+ await Registry.handler(restArgs)
20
+ return
21
+ }
17
22
  if (command === 'cms') {
18
23
  Cms.handler(restArgs)
19
24
  return
@@ -9,6 +9,7 @@ import {
9
9
  } from './resolve-config.js'
10
10
  import { maybeUploadResourceTemplatesAfterPublish } from './resource-templates-after-publish.js'
11
11
  import { maybeUploadSiteArtifactsAfterPublish } from './site-artifacts-after-publish.js'
12
+ import { requireCmsAuthentication } from '../cms/upload-resource-templates.js'
12
13
 
13
14
  const DEPLOYMENT_TOOLS = '@ossy/deployment-tools'
14
15
 
@@ -16,8 +17,6 @@ function runNpxDeploymentTools (deploymentArgs) {
16
17
  const args = ['--yes', DEPLOYMENT_TOOLS, 'deployment', ...deploymentArgs]
17
18
  logInfo({ message: `[@ossy/cli] publish: npx ${DEPLOYMENT_TOOLS} deployment ${deploymentArgs[0]} …` })
18
19
  return new Promise((resolvePromise, reject) => {
19
- // Do not use shell: true — Node joins argv unsafely for sh -c (breaks scoped
20
- // package names like @ossy/deployment-tools) and triggers DEP0190 on recent Node.
21
20
  const npx = process.platform === 'win32' ? 'npx.cmd' : 'npx'
22
21
  const child = spawn(npx, args, { stdio: 'inherit' })
23
22
  child.on('error', reject)
@@ -31,11 +30,13 @@ function runNpxDeploymentTools (deploymentArgs) {
31
30
  /**
32
31
  * Publish container deployments: default = one site (`deployment deploy`);
33
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.
34
37
  */
35
38
  export async function publish (options) {
36
39
  const parsedArgs = arg({
37
- '--username': String,
38
- '-u': '--username',
39
40
  '--authentication': String,
40
41
  '-a': '--authentication',
41
42
  '--domain': String,
@@ -53,20 +54,28 @@ export async function publish (options) {
53
54
  '--skip-site-artifacts': Boolean,
54
55
  '--site-artifacts-build-dir': String,
55
56
  '--api-url': String,
56
- '--cms-authentication': String
57
57
  }, { argv: options })
58
58
 
59
- const username = parsedArgs['--username']
60
- const authentication = parsedArgs['--authentication']
61
59
  const platformsPath = parsedArgs['--platforms-path']
62
60
  const deploymentsPath = parsedArgs['--deployments-path']
63
61
 
64
- if (!username || !authentication) {
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) {
65
71
  logError({
66
- message: '[@ossy/cli] publish: --username (-u) and --authentication (-a) are required.'
72
+ message:
73
+ e?.message
74
+ || '[@ossy/cli] publish: pass --authentication (-a) or set OSSY_API_KEY (Ossy API JWT).',
67
75
  })
68
76
  process.exit(1)
69
77
  }
78
+
70
79
  if (!platformsPath || !deploymentsPath) {
71
80
  logError({
72
81
  message: '[@ossy/cli] publish: --platforms-path (-pp) and --deployments-path (-dp) are required.'
@@ -86,9 +95,6 @@ export async function publish (options) {
86
95
  const skipResourceTemplates = parsedArgs['--skip-resource-templates']
87
96
  const skipSiteArtifacts = parsedArgs['--skip-site-artifacts']
88
97
  const siteArtifactsBuildDir = parsedArgs['--site-artifacts-build-dir']
89
- const apiUrlForTemplates = parsedArgs['--api-url']
90
- const cmsAuthentication =
91
- parsedArgs['--cms-authentication'] || authentication
92
98
 
93
99
  const fromConfig = configPath ? readWebsiteConfigDeployFields(configPath) : {}
94
100
 
@@ -103,8 +109,6 @@ export async function publish (options) {
103
109
  logInfo({ message: `[@ossy/cli] publish --all: platform=${targetPlatform}` })
104
110
  await runNpxDeploymentTools([
105
111
  'deploy-all',
106
- '-u', username,
107
- '-a', authentication,
108
112
  '-p', targetPlatform,
109
113
  '--platforms-path', platformsPath,
110
114
  '--deployments-path', deploymentsPath,
@@ -112,14 +116,14 @@ export async function publish (options) {
112
116
  if (!skipResourceTemplates && configPath) {
113
117
  await maybeUploadResourceTemplatesAfterPublish({
114
118
  configPath,
115
- cmsToken: cmsAuthentication,
119
+ cmsToken: apiToken,
116
120
  apiUrlFlag: apiUrlForTemplates,
117
121
  })
118
122
  }
119
123
  if (!skipSiteArtifacts && configPath) {
120
124
  await maybeUploadSiteArtifactsAfterPublish({
121
125
  configPath,
122
- cmsToken: cmsAuthentication,
126
+ cmsToken: apiToken,
123
127
  apiUrlFlag: apiUrlForTemplates,
124
128
  buildDir: siteArtifactsBuildDir,
125
129
  })
@@ -152,8 +156,6 @@ export async function publish (options) {
152
156
 
153
157
  await runNpxDeploymentTools([
154
158
  'deploy',
155
- '-u', username,
156
- '-a', authentication,
157
159
  '-d', targetDomain,
158
160
  '-p', targetPlatform,
159
161
  '--platforms-path', platformsPath,
@@ -163,14 +165,14 @@ export async function publish (options) {
163
165
  if (!skipResourceTemplates && configPath) {
164
166
  await maybeUploadResourceTemplatesAfterPublish({
165
167
  configPath,
166
- cmsToken: cmsAuthentication,
168
+ cmsToken: apiToken,
167
169
  apiUrlFlag: apiUrlForTemplates,
168
170
  })
169
171
  }
170
172
  if (!skipSiteArtifacts && configPath) {
171
173
  await maybeUploadSiteArtifactsAfterPublish({
172
174
  configPath,
173
- cmsToken: cmsAuthentication,
175
+ cmsToken: apiToken,
174
176
  apiUrlFlag: apiUrlForTemplates,
175
177
  buildDir: siteArtifactsBuildDir,
176
178
  })
@@ -63,7 +63,7 @@ export async function maybeUploadResourceTemplatesAfterPublish ({
63
63
  : ''
64
64
  const hint401 =
65
65
  response.status === 401
66
- ? ' Use an Ossy API JWT with --cms-authentication (repo secret OSSY_API_KEY in CI), not the GitHub deploy token.'
66
+ ? ' Use an Ossy API JWT with --authentication (or OSSY_API_KEY in CI), not a non-API deploy token.'
67
67
  : ''
68
68
  throw new Error(
69
69
  `Resource template upload failed: HTTP ${response.status}${hint404}${hint401}${
@@ -0,0 +1,16 @@
1
+ import { ecrPushCredentials } from './ecr-push-credentials.js'
2
+
3
+ /**
4
+ * @param {string[]} args argv after `registry`
5
+ */
6
+ export async function handler (args) {
7
+ const [sub, ...rest] = args
8
+ if (sub === 'ecr-push-credentials') {
9
+ await ecrPushCredentials(rest)
10
+ return
11
+ }
12
+ console.error(
13
+ '[@ossy/cli] registry: unknown subcommand. Usage: registry ecr-push-credentials [--format json|github-actions] ...'
14
+ )
15
+ process.exit(1)
16
+ }
@@ -0,0 +1,108 @@
1
+ import arg from 'arg'
2
+ import { appendFileSync } from 'fs'
3
+ import { requireCmsAuthentication } from '../cms/upload-resource-templates.js'
4
+
5
+ const DEFAULT_API_URL = 'https://api.ossy.se/api/v0'
6
+
7
+ /**
8
+ * POST /registry/ecr/push-credentials and print docker/registry fields.
9
+ *
10
+ * @param {string[]} options argv after `registry ecr-push-credentials`
11
+ */
12
+ export async function ecrPushCredentials (options) {
13
+ const parsed = arg(
14
+ {
15
+ '--authentication': String,
16
+ '-a': '--authentication',
17
+ '--workspace-id': String,
18
+ '-w': '--workspace-id',
19
+ '--api-url': String,
20
+ '--format': String,
21
+ },
22
+ { argv: options }
23
+ )
24
+
25
+ let token
26
+ try {
27
+ token = requireCmsAuthentication(
28
+ parsed['--authentication'] || process.env.OSSY_API_KEY,
29
+ 'registry ecr-push-credentials'
30
+ )
31
+ } catch (e) {
32
+ console.error(e?.message || '[@ossy/cli] registry ecr-push-credentials: need --authentication (-a) or OSSY_API_KEY')
33
+ process.exit(1)
34
+ }
35
+
36
+ const workspaceId = parsed['--workspace-id'] || process.env.OSSY_WORKSPACE_ID || ''
37
+ if (!workspaceId.trim()) {
38
+ console.error('[@ossy/cli] registry ecr-push-credentials: pass --workspace-id (-w) or set OSSY_WORKSPACE_ID')
39
+ process.exit(1)
40
+ }
41
+
42
+ const apiUrl = (parsed['--api-url'] || process.env.OSSY_API_URL || DEFAULT_API_URL).replace(/\/$/, '')
43
+ const format = parsed['--format'] || 'json'
44
+
45
+ const res = await fetch(`${apiUrl}/registry/ecr/push-credentials`, {
46
+ method: 'POST',
47
+ headers: {
48
+ authorization: token,
49
+ workspaceId: workspaceId.trim(),
50
+ 'content-type': 'application/json',
51
+ },
52
+ body: '{}',
53
+ })
54
+
55
+ const text = await res.text()
56
+ let data
57
+ try {
58
+ data = JSON.parse(text)
59
+ } catch {
60
+ console.error(`[@ossy/cli] registry ecr-push-credentials: non-JSON response (${res.status})`)
61
+ console.error(text)
62
+ process.exit(1)
63
+ }
64
+
65
+ if (!res.ok) {
66
+ console.error(`[@ossy/cli] registry ecr-push-credentials: HTTP ${res.status}`)
67
+ console.error(text)
68
+ process.exit(1)
69
+ }
70
+
71
+ const { registry, username, password, expiresAt } = data
72
+ if (
73
+ typeof registry !== 'string' ||
74
+ !registry ||
75
+ typeof username !== 'string' ||
76
+ !username ||
77
+ typeof password !== 'string' ||
78
+ !password
79
+ ) {
80
+ console.error('[@ossy/cli] registry ecr-push-credentials: invalid response (expected registry, username, password)')
81
+ console.error(JSON.stringify(data))
82
+ process.exit(1)
83
+ }
84
+
85
+ if (format === 'github-actions') {
86
+ const out = process.env.GITHUB_OUTPUT
87
+ if (!out) {
88
+ console.error('[@ossy/cli] registry ecr-push-credentials: --format github-actions requires GITHUB_OUTPUT')
89
+ process.exit(1)
90
+ }
91
+ console.log(`::add-mask::${password}`)
92
+ appendFileSync(out, `registry=${registry}\n`, 'utf8')
93
+ appendFileSync(out, `username=${username}\n`, 'utf8')
94
+ appendFileSync(out, `password=${password}\n`, 'utf8')
95
+ if (expiresAt != null) {
96
+ appendFileSync(out, `expires_at=${String(expiresAt)}\n`, 'utf8')
97
+ }
98
+ return
99
+ }
100
+
101
+ if (format === 'json') {
102
+ console.log(JSON.stringify({ registry, username, password, expiresAt }, null, 2))
103
+ return
104
+ }
105
+
106
+ console.error(`[@ossy/cli] registry ecr-push-credentials: unknown --format ${format} (use json or github-actions)`)
107
+ process.exit(1)
108
+ }