@ossy/cli 0.16.8 → 0.16.10
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
|
@@ -9,7 +9,7 @@ Unified CLI for the Ossy platform: app dev/build and CMS workflows.
|
|
|
9
9
|
| `init [dir]` | Scaffold a new Ossy app (default: current directory) |
|
|
10
10
|
| `dev` | Start dev server with watch (uses `src/*.page.jsx` or `src/pages.jsx`, `src/config.js`) |
|
|
11
11
|
| `build` | Production build |
|
|
12
|
-
| `publish` | Queue a container deployment via `@ossy/deployment-tools
|
|
12
|
+
| `publish` | Queue a container deployment via `@ossy/deployment-tools` (**temporary**; see below), then upload `resourceTemplates` and **site build artifacts** (S3 presign + CMS resource) when `workspaceId` is set |
|
|
13
13
|
| `cms upload` | Upload resource templates only (same API as publish’s upload step) |
|
|
14
14
|
| `cms validate` | Validate ossy config and resource templates |
|
|
15
15
|
|
|
@@ -26,6 +26,25 @@ Options: `--pages`, `--config`, `--destination`. See `@ossy/app` for details.
|
|
|
26
26
|
|
|
27
27
|
Publishes a site by sending a deployment request to your platform queue (same as `npx @ossy/deployment-tools deployment deploy`). Run from the **website package** directory (where `src/config.js` lives) so domain/platform can be read automatically.
|
|
28
28
|
|
|
29
|
+
### Temporary: no execution of `src/config.js` for CMS steps
|
|
30
|
+
|
|
31
|
+
After deploy succeeds, **`publish`** still needs **`workspaceId`**, **`apiUrl`** (optional, for absolute URLs), and **`resourceTemplates`** from `src/config.js`. Those values are **not** loaded with `import()` anymore: running the real config in plain Node would execute imports such as `@ossy/themes`, which are only meant to be resolved during **`ossy build`** (Rollup).
|
|
32
|
+
|
|
33
|
+
Instead, the CLI uses a **temporary** pipeline:
|
|
34
|
+
|
|
35
|
+
- **`workspaceId`** and **`apiUrl`** — read with **string-literal regexes** (same idea as deploy hints).
|
|
36
|
+
- **`resourceTemplates`** — parsed with **`@babel/parser`** from `export default { … }` when that array is **JSON-like literals only** (no function calls, variables, spreads, or template literals in that subtree).
|
|
37
|
+
|
|
38
|
+
If `resourceTemplates` cannot be extracted, template upload is skipped. This is a **stopgap** until publish is driven by build output or platform events (see **Future direction**).
|
|
39
|
+
|
|
40
|
+
### Future direction (planned)
|
|
41
|
+
|
|
42
|
+
The explicit **`deployment deploy`** step (SQS / deployment queue from **`@ossy/deployment-tools`**) is **intended to go away**: the platform should **react to a website upload / artifact event** (e.g. after the container image or site bundle is published) and roll out without the CLI calling deploy. When that exists, **`publish`** can shrink to CMS-only steps (resource templates + site artifacts), or those can move to separate workflows entirely. Treat the current **deploy + follow-ups** combo as **temporary** coupling.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
Details:
|
|
47
|
+
|
|
29
48
|
```bash
|
|
30
49
|
cd packages/my-website
|
|
31
50
|
npx @ossy/cli publish \
|
|
@@ -39,8 +58,8 @@ npx @ossy/cli publish \
|
|
|
39
58
|
- **`--config`** — Path to another `config.js` if not `./src/config.js`.
|
|
40
59
|
- If `platform` is omitted but `domain` is set (from flags or config), it is inferred from `deployments.json` when that domain appears under exactly one `targetDeploymentPlatform`.
|
|
41
60
|
- **`--all`** — Runs `deployment deploy-all` for the platform; requires `--platform` or `platform` in config.
|
|
42
|
-
- **Resource templates** — After a successful deploy, the CLI
|
|
43
|
-
- **Site artifacts** — If **`workspaceId`** is set and **`build/`** exists next to `src/` (i.e. run **`npm run build`** first), the CLI calls **`/site-artifacts/presign-batch`**, **PUT**s each file to S3, then **`/site-artifacts/commit-batch`** so a **`@ossy/platform/site-artifact-batch`** resource is created in the CMS. Skipped with **`--skip-site-artifacts`**, when there is no **`build/`**, or when **`workspaceId`** is missing. Override the build output directory with **`--site-artifacts-build-dir`** (absolute or cwd-relative).
|
|
61
|
+
- **Resource templates** — After a successful deploy, the CLI reads **`workspaceId`** / **`resourceTemplates`** from `./src/config.js` (or `--config`) using the **static extraction** described above (not `import()`). If **`workspaceId`** is set and **`resourceTemplates`** is a non-empty array, they are **POST**ed to **`{api}/resource-templates`** with the **`workspaceId`** header (same as the CMS). Skipped when **`--skip-resource-templates`** is set, or when `workspaceId` / `resourceTemplates` are missing or not extractable.
|
|
62
|
+
- **Site artifacts** — Uses the same **static `workspaceId` / `apiUrl` extraction** as resource templates. If **`workspaceId`** is set and **`build/`** exists next to `src/` (i.e. run **`npm run build`** first), the CLI calls **`/site-artifacts/presign-batch`**, **PUT**s each file to S3, then **`/site-artifacts/commit-batch`** so a **`@ossy/platform/site-artifact-batch`** resource is created in the CMS. Skipped with **`--skip-site-artifacts`**, when there is no **`build/`**, or when **`workspaceId`** is missing. Override the build output directory with **`--site-artifacts-build-dir`** (absolute or cwd-relative).
|
|
44
63
|
- **`--cms-authentication`** — Optional token used only for CMS/API steps (templates + site artifacts); defaults to **`--authentication`** (deployment token).
|
|
45
64
|
- **`--api-url`** — Optional API base for CMS calls (e.g. `https://api.ossy.se/api/v0`). Otherwise **`OSSY_API_URL`**, else an **absolute** `apiUrl` from config, else `https://api.ossy.se/api/v0`. Relative app `apiUrl` values (e.g. `/@ossy`) are ignored unless you pass **`--api-url`** or set **`OSSY_API_URL`**.
|
|
46
65
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/cli",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.10",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ossy-se/packages.git"
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"bin": "./src/index.js",
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@
|
|
20
|
+
"@babel/parser": "^7.28.6",
|
|
21
|
+
"@ossy/app": "^0.15.10",
|
|
21
22
|
"arg": "^5.0.2",
|
|
22
23
|
"glob": "^10.3.10"
|
|
23
24
|
},
|
|
@@ -29,5 +30,5 @@
|
|
|
29
30
|
"/src",
|
|
30
31
|
"README.md"
|
|
31
32
|
],
|
|
32
|
-
"gitHead": "
|
|
33
|
+
"gitHead": "cc54553550674b98f8a53bb3249307e06c4daa82"
|
|
33
34
|
}
|
|
@@ -1,8 +1,138 @@
|
|
|
1
|
+
import { readFileSync } from 'fs'
|
|
1
2
|
import { resolve } from 'path'
|
|
3
|
+
import { parse } from '@babel/parser'
|
|
2
4
|
import { pathToFileURL } from 'url'
|
|
3
5
|
|
|
6
|
+
/** @returns {unknown | undefined} `undefined` if the AST cannot be reduced to JSON-like data */
|
|
7
|
+
function astNodeToLiteralValue (node) {
|
|
8
|
+
if (!node) return undefined
|
|
9
|
+
switch (node.type) {
|
|
10
|
+
case 'StringLiteral':
|
|
11
|
+
case 'BooleanLiteral':
|
|
12
|
+
return node.value
|
|
13
|
+
case 'NumericLiteral':
|
|
14
|
+
return node.value
|
|
15
|
+
case 'NullLiteral':
|
|
16
|
+
return null
|
|
17
|
+
case 'UnaryExpression':
|
|
18
|
+
if (node.operator === '-' && node.argument?.type === 'NumericLiteral') {
|
|
19
|
+
return -node.argument.value
|
|
20
|
+
}
|
|
21
|
+
return undefined
|
|
22
|
+
case 'ObjectExpression': {
|
|
23
|
+
const o = Object.create(null)
|
|
24
|
+
for (const p of node.properties) {
|
|
25
|
+
if (p.type !== 'ObjectProperty' || p.computed) return undefined
|
|
26
|
+
if (p.shorthand) return undefined
|
|
27
|
+
const key =
|
|
28
|
+
p.key.type === 'Identifier'
|
|
29
|
+
? p.key.name
|
|
30
|
+
: p.key.type === 'StringLiteral'
|
|
31
|
+
? p.key.value
|
|
32
|
+
: undefined
|
|
33
|
+
if (key === undefined) return undefined
|
|
34
|
+
const v = astNodeToLiteralValue(p.value)
|
|
35
|
+
if (v === undefined) return undefined
|
|
36
|
+
o[key] = v
|
|
37
|
+
}
|
|
38
|
+
return o
|
|
39
|
+
}
|
|
40
|
+
case 'ArrayExpression': {
|
|
41
|
+
const arr = []
|
|
42
|
+
for (const el of node.elements) {
|
|
43
|
+
if (el === null) {
|
|
44
|
+
arr.push(null)
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
47
|
+
if (el.type === 'SpreadElement') return undefined
|
|
48
|
+
const v = astNodeToLiteralValue(el)
|
|
49
|
+
if (v === undefined) return undefined
|
|
50
|
+
arr.push(v)
|
|
51
|
+
}
|
|
52
|
+
return arr
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
return undefined
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Reads `resourceTemplates` from `export default { ... }` when it is a literal array
|
|
61
|
+
* (no imports, calls, or templates). Matches typical OSSY website configs.
|
|
62
|
+
*
|
|
63
|
+
* @param {string} source
|
|
64
|
+
* @returns {unknown[] | undefined}
|
|
65
|
+
*/
|
|
66
|
+
function extractResourceTemplatesFromSource (source) {
|
|
67
|
+
const ast = parse(source, {
|
|
68
|
+
sourceType: 'module',
|
|
69
|
+
allowAwaitOutsideFunction: true,
|
|
70
|
+
errorRecovery: false,
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
let obj = null
|
|
74
|
+
for (const stmt of ast.program.body) {
|
|
75
|
+
if (stmt.type !== 'ExportDefaultDeclaration') continue
|
|
76
|
+
const decl = stmt.declaration
|
|
77
|
+
if (decl.type === 'ObjectExpression') {
|
|
78
|
+
obj = decl
|
|
79
|
+
break
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
if (!obj) return undefined
|
|
83
|
+
|
|
84
|
+
for (const prop of obj.properties) {
|
|
85
|
+
if (prop.type !== 'ObjectProperty' || prop.computed) continue
|
|
86
|
+
const key =
|
|
87
|
+
prop.key.type === 'Identifier'
|
|
88
|
+
? prop.key.name
|
|
89
|
+
: prop.key.type === 'StringLiteral'
|
|
90
|
+
? prop.key.value
|
|
91
|
+
: null
|
|
92
|
+
if (key !== 'resourceTemplates') continue
|
|
93
|
+
if (prop.value.type !== 'ArrayExpression') return undefined
|
|
94
|
+
const v = astNodeToLiteralValue(prop.value)
|
|
95
|
+
return Array.isArray(v) ? v : undefined
|
|
96
|
+
}
|
|
97
|
+
return undefined
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Reads fields needed for `ossy publish` follow-up steps **without executing** `config.js`.
|
|
102
|
+
* Avoids resolving imports such as `@ossy/themes` in CI (publish runs after deploy, outside Rollup).
|
|
103
|
+
*
|
|
104
|
+
* **Temporary** — prefer a build-time `publish-meta.json` or platform events once deploy is decoupled
|
|
105
|
+
* from the CLI; see `packages/cli/README.md` → *Publish* → *Temporary: no execution of src/config.js*.
|
|
106
|
+
*
|
|
107
|
+
* @param {string} configPath Absolute or cwd-relative path to `src/config.js`
|
|
108
|
+
* @returns {{ workspaceId?: string, apiUrl?: string, resourceTemplates?: unknown[] }}
|
|
109
|
+
*/
|
|
110
|
+
export function readPublishFieldsFromWebsiteConfig (configPath) {
|
|
111
|
+
const abs = resolve(configPath)
|
|
112
|
+
const source = readFileSync(abs, 'utf8')
|
|
113
|
+
|
|
114
|
+
const pickString = (key) => {
|
|
115
|
+
const m = source.match(new RegExp(`\\b${key}\\s*:\\s*['"]([^'"]*)['"]`, 'm'))
|
|
116
|
+
return m ? m[1] : undefined
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
let resourceTemplates
|
|
120
|
+
try {
|
|
121
|
+
resourceTemplates = extractResourceTemplatesFromSource(source)
|
|
122
|
+
} catch {
|
|
123
|
+
resourceTemplates = undefined
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
workspaceId: pickString('workspaceId'),
|
|
128
|
+
apiUrl: pickString('apiUrl'),
|
|
129
|
+
resourceTemplates,
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
4
133
|
/**
|
|
5
|
-
*
|
|
134
|
+
* @deprecated Prefer {@link readPublishFieldsFromWebsiteConfig} for publish — dynamic import
|
|
135
|
+
* runs all side effects and `import`s in config (e.g. `@ossy/themes`), which breaks in plain Node.
|
|
6
136
|
* @param {string} configPath Absolute or cwd-relative path to config file
|
|
7
137
|
*/
|
|
8
138
|
export async function loadWebsiteConfig (configPath) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logInfo } from '../log.js'
|
|
2
|
-
import {
|
|
2
|
+
import { readPublishFieldsFromWebsiteConfig } from './load-website-config.js'
|
|
3
3
|
import {
|
|
4
4
|
postResourceTemplates,
|
|
5
5
|
resolveApiBaseUrlForUpload,
|
|
@@ -13,9 +13,9 @@ export async function maybeUploadResourceTemplatesAfterPublish ({
|
|
|
13
13
|
cmsToken,
|
|
14
14
|
apiUrlFlag,
|
|
15
15
|
}) {
|
|
16
|
-
const config =
|
|
17
|
-
const workspaceId = config
|
|
18
|
-
const resourceTemplates = config
|
|
16
|
+
const config = readPublishFieldsFromWebsiteConfig(configPath)
|
|
17
|
+
const workspaceId = config.workspaceId
|
|
18
|
+
const resourceTemplates = config.resourceTemplates
|
|
19
19
|
|
|
20
20
|
if (!workspaceId) {
|
|
21
21
|
logInfo({
|
|
@@ -2,7 +2,7 @@ import { readdir, stat, readFile } from 'fs/promises'
|
|
|
2
2
|
import { existsSync } from 'fs'
|
|
3
3
|
import path from 'path'
|
|
4
4
|
import { logInfo } from '../log.js'
|
|
5
|
-
import {
|
|
5
|
+
import { readPublishFieldsFromWebsiteConfig } from './load-website-config.js'
|
|
6
6
|
import { resolveApiBaseUrlForUpload } from '../cms/upload-resource-templates.js'
|
|
7
7
|
|
|
8
8
|
const MAX_FILES = 200
|
|
@@ -83,8 +83,8 @@ export async function maybeUploadSiteArtifactsAfterPublish ({
|
|
|
83
83
|
apiUrlFlag,
|
|
84
84
|
buildDir: buildDirOpt,
|
|
85
85
|
}) {
|
|
86
|
-
const config =
|
|
87
|
-
const workspaceId = config
|
|
86
|
+
const config = readPublishFieldsFromWebsiteConfig(configPath)
|
|
87
|
+
const workspaceId = config.workspaceId
|
|
88
88
|
|
|
89
89
|
if (!workspaceId) {
|
|
90
90
|
logInfo({
|
|
@@ -109,7 +109,7 @@ export async function maybeUploadSiteArtifactsAfterPublish ({
|
|
|
109
109
|
const apiBaseUrl = resolveApiBaseUrlForUpload({
|
|
110
110
|
flag: apiUrlFlag,
|
|
111
111
|
envVar: process.env.OSSY_API_URL,
|
|
112
|
-
configApiUrl: config
|
|
112
|
+
configApiUrl: config.apiUrl,
|
|
113
113
|
})
|
|
114
114
|
|
|
115
115
|
const files = await collectBuildFiles(buildDir)
|