@ossy/cli 0.9.0 → 0.10.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/README.md +57 -15
- package/package.json +4 -3
- package/src/cms/cli.js +145 -0
- package/src/index.js +30 -7
- package/src/init/cli.js +73 -0
- package/src/resource-templates/cli.js +0 -65
package/README.md
CHANGED
|
@@ -1,45 +1,87 @@
|
|
|
1
1
|
# @ossy/cli
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Unified CLI for the Ossy platform: app dev/build and CMS workflows.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Commands
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
| Command | Description |
|
|
8
|
+
|---------|-------------|
|
|
9
|
+
| `init [dir]` | Scaffold a new Ossy app (default: current directory) |
|
|
10
|
+
| `dev` | Start dev server with watch (uses `src/*.page.jsx` or `src/pages.jsx`, `src/config.js`) |
|
|
11
|
+
| `build` | Production build |
|
|
12
|
+
| `cms upload` | Upload resource templates to your workspace |
|
|
13
|
+
| `cms validate` | Validate ossy config and resource templates |
|
|
14
|
+
|
|
15
|
+
## App: dev & build
|
|
9
16
|
|
|
10
17
|
```bash
|
|
11
|
-
npx @ossy/cli
|
|
18
|
+
npx @ossy/cli dev
|
|
19
|
+
npx @ossy/cli build
|
|
12
20
|
```
|
|
13
21
|
|
|
14
|
-
|
|
22
|
+
Options: `--pages`, `--config`, `--destination`. See `@ossy/app` for details.
|
|
23
|
+
|
|
24
|
+
## CMS: upload
|
|
25
|
+
|
|
26
|
+
Upload resource templates to your workspace so they can be used in the UI.
|
|
15
27
|
|
|
16
28
|
```bash
|
|
29
|
+
npx @ossy/cli cms upload --authentication <cms-api-token> --ossy-file ossy.json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Config consistency
|
|
33
|
+
|
|
34
|
+
- **App** (`dev`, `build`): `--config` → app config (`src/config.js` by default)
|
|
35
|
+
- **CMS** (`cms upload`): `--ossy-file` → workspace config with `workspaceId` and `resourceTemplates`
|
|
36
|
+
|
|
37
|
+
### Workflow example
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
17
40
|
name: "[CMS] Upload resource templates"
|
|
18
41
|
|
|
19
42
|
on:
|
|
20
43
|
workflow_dispatch:
|
|
21
44
|
|
|
22
45
|
jobs:
|
|
23
|
-
|
|
24
46
|
upload-resource-templates:
|
|
25
47
|
name: Upload resource templates
|
|
26
48
|
runs-on: ubuntu-latest
|
|
27
49
|
steps:
|
|
28
50
|
- uses: actions/checkout@v2
|
|
29
|
-
|
|
30
51
|
- uses: actions/setup-node@v2
|
|
31
52
|
with:
|
|
32
53
|
node-version: "16"
|
|
33
|
-
|
|
34
54
|
- name: Upload
|
|
35
55
|
run: |
|
|
36
|
-
npx --yes @ossy/cli cms
|
|
56
|
+
npx --yes @ossy/cli cms upload \
|
|
37
57
|
--authentication ${{ secrets.CMS_API_TOKEN }} \
|
|
38
|
-
--ossy-file ossy.json
|
|
58
|
+
--ossy-file ossy.json
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### cms validate
|
|
62
|
+
|
|
63
|
+
Validate an ossy config file before uploading:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npx @ossy/cli cms validate --ossy-file ossy.json
|
|
39
67
|
```
|
|
40
68
|
|
|
41
|
-
|
|
69
|
+
Defaults to `ossy.json` in the current directory when `--ossy-file` is omitted.
|
|
70
|
+
|
|
71
|
+
### Arguments
|
|
72
|
+
|
|
42
73
|
| Argument | Description | Required |
|
|
43
|
-
|
|
44
|
-
| --authentication | Your
|
|
45
|
-
| --ossy-file | Path to
|
|
74
|
+
|----------|-------------|----------|
|
|
75
|
+
| --authentication, -a | Your CMS API token | Yes (upload only) |
|
|
76
|
+
| --ossy-file | Path to file with `workspaceId` and `resourceTemplates` | Yes (upload), optional (validate) |
|
|
77
|
+
|
|
78
|
+
## init
|
|
79
|
+
|
|
80
|
+
Scaffold a new Ossy app:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @ossy/cli init
|
|
84
|
+
npx @ossy/cli init my-app
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Creates `src/pages.jsx`, `src/config.js`, and `package.json` (if missing).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ossy/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/ossy-se/packages.git"
|
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
"main": "./src/index.js",
|
|
11
11
|
"type": "module",
|
|
12
12
|
"scripts": {
|
|
13
|
-
"test": "
|
|
13
|
+
"test": "node --experimental-vm-modules ../node_modules/jest/bin/jest.js --verbose",
|
|
14
14
|
"build": "echo \"The build step is not required when using JavaScript!\" && exit 0"
|
|
15
15
|
},
|
|
16
16
|
"author": "Ossy <yourfriends@ossy.se> (https://ossy.se)",
|
|
17
17
|
"license": "MIT",
|
|
18
18
|
"bin": "./src/index.js",
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@ossy/app": "^0.10.0",
|
|
20
21
|
"arg": "^5.0.2"
|
|
21
22
|
},
|
|
22
23
|
"publishConfig": {
|
|
@@ -27,5 +28,5 @@
|
|
|
27
28
|
"/src",
|
|
28
29
|
"README.md"
|
|
29
30
|
],
|
|
30
|
-
"gitHead": "
|
|
31
|
+
"gitHead": "3019a8da590435ba6f10d7d861addeba011baf5d"
|
|
31
32
|
}
|
package/src/cms/cli.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { resolve } from 'path'
|
|
2
|
+
import { readFileSync, existsSync } from 'fs'
|
|
3
|
+
import arg from 'arg'
|
|
4
|
+
import { logInfo, logError } from '../log.js'
|
|
5
|
+
|
|
6
|
+
const Api = {
|
|
7
|
+
uploadResourceTemplates: (apiUrl, token, workspaceId, resourceTemplates) => {
|
|
8
|
+
const endpoint = `${apiUrl}/workspaces/${workspaceId}/resource-templates`
|
|
9
|
+
const fetchOptions = {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
12
|
+
body: JSON.stringify(resourceTemplates)
|
|
13
|
+
}
|
|
14
|
+
return fetch(endpoint, fetchOptions)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const resolveConfigImport = (filePath) =>
|
|
19
|
+
filePath.endsWith('json')
|
|
20
|
+
? Promise.resolve(JSON.parse(readFileSync(filePath, 'utf8')))
|
|
21
|
+
: import(filePath)
|
|
22
|
+
|
|
23
|
+
const upload = (options) => {
|
|
24
|
+
const parsedArgs = arg({
|
|
25
|
+
'--authentication': String,
|
|
26
|
+
'--a': '--authentication',
|
|
27
|
+
'--ossy-file': String,
|
|
28
|
+
}, { argv: options })
|
|
29
|
+
|
|
30
|
+
const token = parsedArgs['--authentication']
|
|
31
|
+
const ossyFilePath = parsedArgs['--ossy-file']
|
|
32
|
+
|
|
33
|
+
if (!token) {
|
|
34
|
+
logError({ message: '[@ossy/cli] No token provided. Use --authentication or -a' })
|
|
35
|
+
process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
if (!ossyFilePath) {
|
|
38
|
+
logError({ message: '[@ossy/cli] No config file provided. Use --ossy-file' })
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
logInfo({ message: '[@ossy/cli] Reading config...' })
|
|
43
|
+
const filePath = resolve(ossyFilePath)
|
|
44
|
+
|
|
45
|
+
return resolveConfigImport(filePath)
|
|
46
|
+
.then((module) => {
|
|
47
|
+
const config = module?.default ?? module
|
|
48
|
+
const apiUrl = config?.apiUrl || 'https://api.ossy.se/api/v0'
|
|
49
|
+
const workspaceId = config?.workspaceId
|
|
50
|
+
const resourceTemplates = config?.resourceTemplates
|
|
51
|
+
|
|
52
|
+
if (!workspaceId) {
|
|
53
|
+
logError({ message: '[@ossy/cli] No workspaceId in ossy file' })
|
|
54
|
+
process.exit(1)
|
|
55
|
+
}
|
|
56
|
+
if (!resourceTemplates) {
|
|
57
|
+
logError({ message: '[@ossy/cli] No resourceTemplates in ossy file' })
|
|
58
|
+
process.exit(1)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logInfo({ message: '[@ossy/cli] Uploading resource templates...' })
|
|
62
|
+
|
|
63
|
+
return Api.uploadResourceTemplates(apiUrl, token, workspaceId, resourceTemplates)
|
|
64
|
+
.then((response) => {
|
|
65
|
+
if (!response.ok) throw new Error(`Upload failed: ${response.status}`)
|
|
66
|
+
logInfo({ message: '[@ossy/cli] Done' })
|
|
67
|
+
})
|
|
68
|
+
.catch((error) => {
|
|
69
|
+
logError({ message: '[@ossy/cli] Error', error })
|
|
70
|
+
process.exit(1)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
.catch((error) => {
|
|
74
|
+
logError({ message: '[@ossy/cli] Error', error })
|
|
75
|
+
process.exit(1)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const validate = (options) => {
|
|
80
|
+
const parsedArgs = arg({
|
|
81
|
+
'--ossy-file': String,
|
|
82
|
+
}, { argv: options })
|
|
83
|
+
|
|
84
|
+
const ossyFilePath = parsedArgs['--ossy-file']
|
|
85
|
+
const filePath = ossyFilePath ? resolve(ossyFilePath) : resolve('ossy.json')
|
|
86
|
+
|
|
87
|
+
if (!existsSync(filePath)) {
|
|
88
|
+
logError({ message: `[@ossy/cli] File not found: ${filePath}. Use --ossy-file` })
|
|
89
|
+
process.exit(1)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
logInfo({ message: '[@ossy/cli] Validating...' })
|
|
93
|
+
|
|
94
|
+
resolveConfigImport(filePath)
|
|
95
|
+
.then((module) => {
|
|
96
|
+
const config = module?.default ?? module
|
|
97
|
+
const workspaceId = config?.workspaceId
|
|
98
|
+
const resourceTemplates = config?.resourceTemplates
|
|
99
|
+
|
|
100
|
+
const errors = []
|
|
101
|
+
if (!workspaceId) errors.push('Missing workspaceId')
|
|
102
|
+
if (!resourceTemplates) {
|
|
103
|
+
errors.push('Missing resourceTemplates')
|
|
104
|
+
} else if (!Array.isArray(resourceTemplates)) {
|
|
105
|
+
errors.push('resourceTemplates must be an array')
|
|
106
|
+
} else {
|
|
107
|
+
resourceTemplates.forEach((t, i) => {
|
|
108
|
+
if (!t.id) errors.push(`Template ${i}: missing id`)
|
|
109
|
+
if (!t.name) errors.push(`Template ${i}: missing name`)
|
|
110
|
+
if (!t.fields || !Array.isArray(t.fields)) {
|
|
111
|
+
errors.push(`Template ${i}: fields must be an array`)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (errors.length > 0) {
|
|
117
|
+
console.error('[@ossy/cli] Validation failed:')
|
|
118
|
+
errors.forEach((e) => console.error(' -', e))
|
|
119
|
+
process.exit(1)
|
|
120
|
+
}
|
|
121
|
+
logInfo({ message: '[@ossy/cli] Valid' })
|
|
122
|
+
})
|
|
123
|
+
.catch((error) => {
|
|
124
|
+
logError({ message: '[@ossy/cli] Error', error })
|
|
125
|
+
process.exit(1)
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const subcommands = {
|
|
130
|
+
upload,
|
|
131
|
+
validate,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export const handler = ([subcommand, ...options]) => {
|
|
135
|
+
if (!subcommand) {
|
|
136
|
+
logError({ message: '[@ossy/cli] cms: no subcommand. Use: ossy cms upload | validate' })
|
|
137
|
+
process.exit(1)
|
|
138
|
+
}
|
|
139
|
+
const fn = subcommands[subcommand]
|
|
140
|
+
if (!fn) {
|
|
141
|
+
logError({ message: `[@ossy/cli] cms: unknown subcommand "${subcommand}". Use: ossy cms upload | validate` })
|
|
142
|
+
process.exit(1)
|
|
143
|
+
}
|
|
144
|
+
fn(options)
|
|
145
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,11 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import * as
|
|
2
|
+
import { build, dev } from '@ossy/app'
|
|
3
|
+
import * as Cms from './cms/cli.js'
|
|
4
|
+
import * as Init from './init/cli.js'
|
|
4
5
|
|
|
5
|
-
const [
|
|
6
|
+
const [,, command, ...restArgs] = process.argv
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
'
|
|
9
|
-
|
|
8
|
+
if (!command) {
|
|
9
|
+
console.error('[@ossy/cli] No command provided. Usage: ossy dev | build | init | cms <subcommand>')
|
|
10
|
+
process.exit(1)
|
|
11
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
const run = async () => {
|
|
14
|
+
if (command === 'cms') {
|
|
15
|
+
Cms.handler(restArgs)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
if (command === 'init') {
|
|
19
|
+
Init.init(restArgs)
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
if (command === 'dev') {
|
|
23
|
+
await dev(restArgs)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
if (command === 'build') {
|
|
27
|
+
await build(restArgs)
|
|
28
|
+
return
|
|
29
|
+
}
|
|
30
|
+
console.error(`[@ossy/cli] Unknown command: ${command}`)
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
run()
|
package/src/init/cli.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import fs from 'fs'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import { logInfo, logError } from '../log.js'
|
|
4
|
+
|
|
5
|
+
const TEMPLATE_HOME_PAGE = `import React from 'react'
|
|
6
|
+
|
|
7
|
+
export default () => (
|
|
8
|
+
<main style={{ padding: '2rem' }}>
|
|
9
|
+
<h1>Welcome</h1>
|
|
10
|
+
<p>Add new pages by creating *.page.jsx files in src/</p>
|
|
11
|
+
</main>
|
|
12
|
+
)
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
const TEMPLATE_CONFIG = `import { CloudLight } from '@ossy/themes'
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
workspaceId: 'your-workspace-id',
|
|
19
|
+
theme: CloudLight,
|
|
20
|
+
}
|
|
21
|
+
`
|
|
22
|
+
|
|
23
|
+
const TEMPLATE_PACKAGE_JSON = {
|
|
24
|
+
name: 'my-ossy-app',
|
|
25
|
+
version: '0.0.1',
|
|
26
|
+
private: true,
|
|
27
|
+
type: 'module',
|
|
28
|
+
scripts: {
|
|
29
|
+
dev: 'npx @ossy/cli dev',
|
|
30
|
+
build: 'npx @ossy/cli build',
|
|
31
|
+
start: 'node build/server.js',
|
|
32
|
+
},
|
|
33
|
+
dependencies: {
|
|
34
|
+
'@ossy/cli': '^0.9.0',
|
|
35
|
+
'@ossy/app': '^0.9.0',
|
|
36
|
+
'@ossy/connected-components': '^0.9.0',
|
|
37
|
+
'@ossy/design-system': '^0.9.0',
|
|
38
|
+
'@ossy/themes': '^0.9.0',
|
|
39
|
+
'@ossy/router-react': '^0.9.0',
|
|
40
|
+
'@ossy/sdk': '^0.9.0',
|
|
41
|
+
'@ossy/sdk-react': '^0.9.0',
|
|
42
|
+
react: '^19.0.0',
|
|
43
|
+
'react-dom': '^19.0.0',
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const init = (options) => {
|
|
48
|
+
const parsedArgs = options || []
|
|
49
|
+
const targetDir = parsedArgs[0] ? path.resolve(parsedArgs[0]) : process.cwd()
|
|
50
|
+
|
|
51
|
+
const srcDir = path.join(targetDir, 'src')
|
|
52
|
+
const homePagePath = path.join(srcDir, 'home.page.jsx')
|
|
53
|
+
if (fs.existsSync(homePagePath) || fs.existsSync(path.join(targetDir, 'src', 'pages.jsx'))) {
|
|
54
|
+
logError({ message: '[@ossy/cli] Project already exists (src/home.page.jsx or src/pages.jsx found)' })
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
logInfo({ message: `[@ossy/cli] Creating project in ${targetDir}` })
|
|
59
|
+
|
|
60
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
61
|
+
|
|
62
|
+
fs.writeFileSync(homePagePath, TEMPLATE_HOME_PAGE)
|
|
63
|
+
fs.writeFileSync(path.join(srcDir, 'config.js'), TEMPLATE_CONFIG)
|
|
64
|
+
|
|
65
|
+
const pkgPath = path.join(targetDir, 'package.json')
|
|
66
|
+
if (!fs.existsSync(pkgPath)) {
|
|
67
|
+
fs.writeFileSync(pkgPath, JSON.stringify(TEMPLATE_PACKAGE_JSON, null, 2))
|
|
68
|
+
} else {
|
|
69
|
+
logInfo({ message: '[@ossy/cli] package.json exists, skipping. Add scripts manually if needed.' })
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
logInfo({ message: '[@ossy/cli] Done. Run: npm install && npm run dev' })
|
|
73
|
+
}
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { resolve } from 'path'
|
|
2
|
-
import { readFileSync } from 'fs'
|
|
3
|
-
import arg from 'arg'
|
|
4
|
-
import { logInfo, logError, logErrorAndReject } from '../log.js'
|
|
5
|
-
|
|
6
|
-
const Api = {
|
|
7
|
-
uploadResourceTemplates: (apiUrl, token, workspaceId, resourceTemplates) => {
|
|
8
|
-
const endpoint = `${apiUrl}/workspaces/${workspaceId}/resource-templates`
|
|
9
|
-
|
|
10
|
-
const fetchOptions = {
|
|
11
|
-
method: 'POST',
|
|
12
|
-
headers: { 'Authorization': token, 'Content-Type': 'application/json' },
|
|
13
|
-
body: JSON.stringify(resourceTemplates)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
return fetch(endpoint, fetchOptions)
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const resolveConfigImport = path => path.endsWith('json')
|
|
21
|
-
? Promise.resolve(JSON.parse(readFileSync(path), 'utf8'))
|
|
22
|
-
: import(path)
|
|
23
|
-
|
|
24
|
-
const importResourceTemplates = options => {
|
|
25
|
-
|
|
26
|
-
const parsedArgs = arg({
|
|
27
|
-
'--authentication': String,
|
|
28
|
-
'--a': '--authentication',
|
|
29
|
-
|
|
30
|
-
'--config': String,
|
|
31
|
-
'-c': '--config',
|
|
32
|
-
}, { argv: options })
|
|
33
|
-
|
|
34
|
-
logInfo({ message: '[CMS] reading files' })
|
|
35
|
-
const token = parsedArgs['--authentication'];
|
|
36
|
-
const filePath = resolve(parsedArgs['--config'])
|
|
37
|
-
|
|
38
|
-
if (!token) return logErrorAndReject({ message: '[CMS] No token provided with --authentication'})
|
|
39
|
-
|
|
40
|
-
return resolveConfigImport(filePath)
|
|
41
|
-
.then(module => {
|
|
42
|
-
const config = module?.default
|
|
43
|
-
const apiUrl = config?.apiUrl || 'https://api.ossy.se/api/v0'
|
|
44
|
-
const workspaceId = config?.workspaceId
|
|
45
|
-
const resourceTemplates = config?.resourceTemplates
|
|
46
|
-
|
|
47
|
-
if (!workspaceId) return logErrorAndReject({ message: '[@ossy/cli] No workspaceId provided in config'})
|
|
48
|
-
if (!resourceTemplates) return logErrorAndReject({ message: '[@ossy/cli] No resource templates provided in config'})
|
|
49
|
-
|
|
50
|
-
logInfo({ message: '[@ossy/cli] uploading resource templates' })
|
|
51
|
-
|
|
52
|
-
Api.uploadResourceTemplates(apiUrl, token, workspaceId, resourceTemplates)
|
|
53
|
-
.then(response => {
|
|
54
|
-
logInfo({ message: '[@ossy/cli] Done' })
|
|
55
|
-
})
|
|
56
|
-
.catch(error => logError({ message: '[@ossy/cli] Error', error }))
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export const handler = ([command, ...options]) => {
|
|
62
|
-
!!command
|
|
63
|
-
? { 'upload': importResourceTemplates }[command](options)
|
|
64
|
-
: logError({ message: '[@ossy/cli] No command provided' })
|
|
65
|
-
}
|