@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.
- package/README.md +193 -51
- package/package.json +4 -3
- package/src/{cms → app}/cli.js +13 -7
- package/src/{publish/load-website-config.js → app/load-config.js} +6 -20
- package/src/app/publish.js +206 -0
- package/src/{cms → app}/upload-resource-templates.js +1 -1
- package/src/auth/cli.js +135 -0
- package/src/call/cli.js +311 -0
- package/src/file.js +99 -0
- package/src/index.js +76 -48
- package/src/registry/ecr-push-credentials.js +9 -8
- package/src/state.js +115 -0
- package/src/upload-dir/cli.js +270 -0
- package/src/workspace/cli.js +84 -0
- package/src/publish/cli.js +0 -180
- package/src/publish/resolve-config.js +0 -44
- package/src/publish/resource-templates-after-publish.js +0 -75
- package/src/publish/site-artifacts-after-publish.js +0 -210
package/src/file.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
/** @type {Record<string, string>} */
|
|
5
|
+
const MIME_BY_EXT = {
|
|
6
|
+
'.js': 'application/javascript',
|
|
7
|
+
'.mjs': 'application/javascript',
|
|
8
|
+
'.cjs': 'application/javascript',
|
|
9
|
+
'.css': 'text/css',
|
|
10
|
+
'.html': 'text/html',
|
|
11
|
+
'.htm': 'text/html',
|
|
12
|
+
'.json': 'application/json',
|
|
13
|
+
'.map': 'application/json',
|
|
14
|
+
'.svg': 'image/svg+xml',
|
|
15
|
+
'.png': 'image/png',
|
|
16
|
+
'.jpg': 'image/jpeg',
|
|
17
|
+
'.jpeg': 'image/jpeg',
|
|
18
|
+
'.gif': 'image/gif',
|
|
19
|
+
'.webp': 'image/webp',
|
|
20
|
+
'.ico': 'image/x-icon',
|
|
21
|
+
'.woff': 'font/woff',
|
|
22
|
+
'.woff2': 'font/woff2',
|
|
23
|
+
'.ttf': 'font/ttf',
|
|
24
|
+
'.txt': 'text/plain',
|
|
25
|
+
'.xml': 'application/xml',
|
|
26
|
+
'.webmanifest': 'application/manifest+json',
|
|
27
|
+
'.pdf': 'application/pdf',
|
|
28
|
+
'.mp4': 'video/mp4',
|
|
29
|
+
'.webm': 'video/webm',
|
|
30
|
+
'.mp3': 'audio/mpeg',
|
|
31
|
+
'.wav': 'audio/wav',
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Best-effort MIME type from extension. Falls back to `application/octet-stream`.
|
|
36
|
+
* @param {string} filePath
|
|
37
|
+
* @returns {string}
|
|
38
|
+
*/
|
|
39
|
+
export function guessContentType (filePath) {
|
|
40
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
41
|
+
return MIME_BY_EXT[ext] || 'application/octet-stream'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* PUT file bytes to a presigned S3 upload URL.
|
|
46
|
+
* Throws on network error or non-2xx response.
|
|
47
|
+
* @param {{ absPath: string, size: number, type: string }} fileMeta
|
|
48
|
+
* @param {string} uploadUrl
|
|
49
|
+
* @returns {Promise<void>}
|
|
50
|
+
*/
|
|
51
|
+
export async function uploadFileToUrl (fileMeta, uploadUrl) {
|
|
52
|
+
const { readFile } = await import('node:fs/promises')
|
|
53
|
+
const body = await readFile(fileMeta.absPath)
|
|
54
|
+
const res = await fetch(uploadUrl, {
|
|
55
|
+
method: 'PUT',
|
|
56
|
+
headers: {
|
|
57
|
+
'Content-Type': fileMeta.type,
|
|
58
|
+
'Content-Length': String(fileMeta.size),
|
|
59
|
+
},
|
|
60
|
+
body,
|
|
61
|
+
})
|
|
62
|
+
if (!res.ok) {
|
|
63
|
+
const t = await res.text().catch(() => '')
|
|
64
|
+
throw new Error(
|
|
65
|
+
`S3 PUT failed: HTTP ${res.status}${t ? ' — ' + t.slice(0, 200) : ''}`
|
|
66
|
+
)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Stat a local file and derive metadata for an upload payload.
|
|
72
|
+
* Throws if the path doesn't exist or isn't a regular file.
|
|
73
|
+
* @param {string} filePath absolute or cwd-relative
|
|
74
|
+
* @returns {{ absPath: string, name: string, size: number, type: string }}
|
|
75
|
+
*/
|
|
76
|
+
export function prepareFileUpload (filePath) {
|
|
77
|
+
if (!filePath || typeof filePath !== 'string') {
|
|
78
|
+
throw new Error('--file: missing path')
|
|
79
|
+
}
|
|
80
|
+
const absPath = path.resolve(filePath)
|
|
81
|
+
let stat
|
|
82
|
+
try {
|
|
83
|
+
stat = fs.statSync(absPath)
|
|
84
|
+
} catch (err) {
|
|
85
|
+
if (err && err.code === 'ENOENT') {
|
|
86
|
+
throw new Error(`--file: not found: ${filePath}`)
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`--file: cannot stat ${filePath}: ${err.message || err}`)
|
|
89
|
+
}
|
|
90
|
+
if (!stat.isFile()) {
|
|
91
|
+
throw new Error(`--file: not a regular file: ${filePath}`)
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
absPath,
|
|
95
|
+
name: path.basename(absPath),
|
|
96
|
+
size: stat.size,
|
|
97
|
+
type: guessContentType(absPath),
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/index.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import fs from 'node:fs'
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { fileURLToPath } from 'node:url'
|
|
5
|
-
import
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
8
|
-
import { publish } from './publish/cli.js'
|
|
5
|
+
import * as Auth from './auth/cli.js'
|
|
6
|
+
import * as Call from './call/cli.js'
|
|
7
|
+
import * as App from './app/cli.js'
|
|
9
8
|
import * as Registry from './registry/cli.js'
|
|
9
|
+
import * as UploadDir from './upload-dir/cli.js'
|
|
10
|
+
import * as Workspace from './workspace/cli.js'
|
|
10
11
|
|
|
11
12
|
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
12
13
|
const pkg = JSON.parse(
|
|
@@ -19,12 +20,18 @@ Command line tool for the Ossy platform.
|
|
|
19
20
|
Usage:
|
|
20
21
|
ossy <command> [options]
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
Local utilities:
|
|
24
|
+
auth <subcommand> Sign in / out and check status: login | logout | status
|
|
25
|
+
workspace <subcommand> Manage active workspace: use | list | current
|
|
26
|
+
app <subcommand> App commands: init | build | upload | validate | publish
|
|
27
|
+
|
|
28
|
+
Action dispatcher (any Ossy SDK action, no per-action wiring needed):
|
|
29
|
+
call <action.id> Run \`ossy call --help\` for the full catalog
|
|
30
|
+
|
|
31
|
+
Workflows (wrap one or more actions with extra ergonomics):
|
|
32
|
+
upload-dir <dir> <loc> Recursively upload a local directory to the CMS
|
|
33
|
+
registry <subcommand> ecr-push-credentials (wraps registry.ecr-push-credentials,
|
|
34
|
+
adds --format github-actions and password masking)
|
|
28
35
|
|
|
29
36
|
Options:
|
|
30
37
|
-h, --help Show this help, or for a command: ossy <command> --help
|
|
@@ -32,51 +39,68 @@ Options:
|
|
|
32
39
|
`
|
|
33
40
|
|
|
34
41
|
const SUBCOMMAND_HELP = {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
Run the production build for the app in the current directory.
|
|
41
|
-
See the @ossy/app README for build options.
|
|
42
|
-
`,
|
|
43
|
-
publish: `ossy publish [options]
|
|
44
|
-
Deploy via @ossy/deployment-tools, then optionally upload resource templates
|
|
45
|
-
and site artifacts.
|
|
42
|
+
auth: `ossy auth <subcommand>
|
|
43
|
+
Subcommands:
|
|
44
|
+
login Save an Ossy API token under ~/.config/ossy/credentials.json
|
|
45
|
+
logout Delete the saved token
|
|
46
|
+
status Print logged-in user, active workspace, and config paths
|
|
46
47
|
|
|
47
|
-
Options:
|
|
48
|
-
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
--skip-site-artifacts Skip post-deploy site-artifact upload
|
|
57
|
-
--site-artifacts-build-dir Override the site build directory
|
|
58
|
-
--api-url API base URL for CMS calls
|
|
48
|
+
Options (login):
|
|
49
|
+
-t, --token Ossy API JWT (otherwise prompts in a TTY)
|
|
50
|
+
--api-url API base URL (default: https://api.ossy.se/api/v0)
|
|
51
|
+
`,
|
|
52
|
+
workspace: `ossy workspace <subcommand>
|
|
53
|
+
Subcommands:
|
|
54
|
+
use <id> Set the active workspace (saved to ~/.config/ossy/config.json)
|
|
55
|
+
list List workspaces the current user can access
|
|
56
|
+
current Print the active workspace id
|
|
59
57
|
`,
|
|
60
|
-
|
|
58
|
+
app: `ossy app <subcommand>
|
|
61
59
|
Subcommands:
|
|
60
|
+
init Scaffold a new Ossy app (default dir: current directory)
|
|
61
|
+
build Production build of the app in the current directory
|
|
62
62
|
upload Upload resource templates from src/config.js to the workspace
|
|
63
|
+
(wraps the SDK action workspaces.import-resource-templates)
|
|
63
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})
|
|
64
67
|
|
|
65
68
|
Options (upload):
|
|
66
|
-
-a, --authentication Ossy API JWT (or set OSSY_API_KEY)
|
|
69
|
+
-a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
|
|
67
70
|
-c, --config Path to src/config.js
|
|
68
|
-
--api-url API base URL for
|
|
71
|
+
--api-url API base URL for API calls
|
|
69
72
|
|
|
70
73
|
Options (validate):
|
|
71
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
|
|
72
94
|
`,
|
|
73
95
|
registry: `ossy registry <subcommand>
|
|
74
96
|
Subcommands:
|
|
75
97
|
ecr-push-credentials Fetch a short-lived ECR password for docker login
|
|
98
|
+
(wraps the SDK action registry.ecr-push-credentials;
|
|
99
|
+
adds --format github-actions and password masking)
|
|
76
100
|
|
|
77
101
|
Options:
|
|
78
|
-
-a, --authentication Ossy API JWT (or set OSSY_API_KEY)
|
|
79
|
-
--workspace-id
|
|
102
|
+
-a, --authentication Ossy API JWT (or set OSSY_API_KEY, or run \`ossy auth login\`)
|
|
103
|
+
-w, --workspace-id Workspace id (or set OSSY_WORKSPACE_ID, or run \`ossy workspace use\`)
|
|
80
104
|
--format json | github-actions
|
|
81
105
|
`,
|
|
82
106
|
}
|
|
@@ -102,24 +126,28 @@ if (restArgs.some(isHelpFlag) && SUBCOMMAND_HELP[command]) {
|
|
|
102
126
|
}
|
|
103
127
|
|
|
104
128
|
const run = async () => {
|
|
105
|
-
if (command === '
|
|
106
|
-
await
|
|
129
|
+
if (command === 'auth') {
|
|
130
|
+
await Auth.handler(restArgs)
|
|
107
131
|
return
|
|
108
132
|
}
|
|
109
|
-
if (command === '
|
|
110
|
-
|
|
133
|
+
if (command === 'workspace') {
|
|
134
|
+
await Workspace.handler(restArgs)
|
|
111
135
|
return
|
|
112
136
|
}
|
|
113
|
-
if (command === '
|
|
114
|
-
|
|
137
|
+
if (command === 'call') {
|
|
138
|
+
await Call.handler(restArgs)
|
|
115
139
|
return
|
|
116
140
|
}
|
|
117
|
-
if (command === '
|
|
118
|
-
await
|
|
141
|
+
if (command === 'upload-dir') {
|
|
142
|
+
await UploadDir.handler(restArgs)
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (command === 'registry') {
|
|
146
|
+
await Registry.handler(restArgs)
|
|
119
147
|
return
|
|
120
148
|
}
|
|
121
|
-
if (command === '
|
|
122
|
-
await
|
|
149
|
+
if (command === 'app') {
|
|
150
|
+
await App.handler(restArgs)
|
|
123
151
|
return
|
|
124
152
|
}
|
|
125
153
|
console.error(`[@ossy/cli] Unknown command: ${command}`)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import arg from 'arg'
|
|
2
2
|
import { appendFileSync } from 'fs'
|
|
3
|
-
import {
|
|
3
|
+
import { requireAppAuthentication } from '../app/upload-resource-templates.js'
|
|
4
|
+
import { getAuth, getWorkspaceId } from '../state.js'
|
|
4
5
|
|
|
5
6
|
const DEFAULT_API_URL = 'https://api.ossy.se/api/v0'
|
|
6
7
|
|
|
@@ -24,18 +25,18 @@ export async function ecrPushCredentials (options) {
|
|
|
24
25
|
|
|
25
26
|
let token
|
|
26
27
|
try {
|
|
27
|
-
token =
|
|
28
|
-
parsed['--authentication']
|
|
28
|
+
token = requireAppAuthentication(
|
|
29
|
+
getAuth({ flag: parsed['--authentication'] }),
|
|
29
30
|
'registry ecr-push-credentials'
|
|
30
31
|
)
|
|
31
32
|
} catch (e) {
|
|
32
|
-
console.error(e?.message || '[@ossy/cli] registry ecr-push-credentials: need --authentication (-a) or
|
|
33
|
+
console.error(e?.message || '[@ossy/cli] registry ecr-push-credentials: need --authentication (-a), OSSY_API_KEY, or `ossy auth login`')
|
|
33
34
|
process.exit(1)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
const workspaceId = parsed['--workspace-id']
|
|
37
|
-
if (!workspaceId
|
|
38
|
-
console.error('[@ossy/cli] registry ecr-push-credentials: pass --workspace-id (-w) or
|
|
37
|
+
const workspaceId = getWorkspaceId({ flag: parsed['--workspace-id'] })
|
|
38
|
+
if (!workspaceId) {
|
|
39
|
+
console.error('[@ossy/cli] registry ecr-push-credentials: pass --workspace-id (-w), set OSSY_WORKSPACE_ID, or run `ossy workspace use <id>`')
|
|
39
40
|
process.exit(1)
|
|
40
41
|
}
|
|
41
42
|
|
|
@@ -46,7 +47,7 @@ export async function ecrPushCredentials (options) {
|
|
|
46
47
|
method: 'POST',
|
|
47
48
|
headers: {
|
|
48
49
|
authorization: token,
|
|
49
|
-
workspaceId
|
|
50
|
+
workspaceId,
|
|
50
51
|
'content-type': 'application/json',
|
|
51
52
|
},
|
|
52
53
|
body: '{}',
|
package/src/state.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
const DEFAULT_API_URL = 'https://api.ossy.se/api/v0'
|
|
6
|
+
|
|
7
|
+
function configDir () {
|
|
8
|
+
const xdg = process.env.XDG_CONFIG_HOME
|
|
9
|
+
if (xdg && xdg.trim()) return path.join(xdg.trim(), 'ossy')
|
|
10
|
+
return path.join(os.homedir(), '.config', 'ossy')
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ensureDir () {
|
|
14
|
+
const dir = configDir()
|
|
15
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
|
|
16
|
+
return dir
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function credentialsPath () { return path.join(configDir(), 'credentials.json') }
|
|
20
|
+
function configPath () { return path.join(configDir(), 'config.json') }
|
|
21
|
+
|
|
22
|
+
/** @returns {{ apiUrl?: string, token?: string } | null} */
|
|
23
|
+
export function readCredentials () {
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(fs.readFileSync(credentialsPath(), 'utf8'))
|
|
26
|
+
} catch {
|
|
27
|
+
return null
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** @param {{ apiUrl?: string, token: string }} creds */
|
|
32
|
+
export function writeCredentials (creds) {
|
|
33
|
+
ensureDir()
|
|
34
|
+
fs.writeFileSync(credentialsPath(), JSON.stringify(creds, null, 2), { mode: 0o600 })
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function deleteCredentials () {
|
|
38
|
+
try { fs.unlinkSync(credentialsPath()) } catch { /* not logged in */ }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @returns {{ workspaceId?: string } | null} */
|
|
42
|
+
export function readConfig () {
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(fs.readFileSync(configPath(), 'utf8'))
|
|
45
|
+
} catch {
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @param {{ workspaceId?: string }} cfg */
|
|
51
|
+
export function writeConfig (cfg) {
|
|
52
|
+
ensureDir()
|
|
53
|
+
fs.writeFileSync(configPath(), JSON.stringify(cfg, null, 2))
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function stripBearer (token) {
|
|
57
|
+
return String(token ?? '').trim().replace(/^Bearer\s+/i, '').trim()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the Ossy API JWT used for authenticated requests.
|
|
62
|
+
* Order: explicit flag > OSSY_API_KEY env > saved credentials.
|
|
63
|
+
* @param {{ flag?: string }} [opts]
|
|
64
|
+
* @returns {string | null}
|
|
65
|
+
*/
|
|
66
|
+
export function getAuth ({ flag } = {}) {
|
|
67
|
+
const fromFlag = stripBearer(flag)
|
|
68
|
+
if (fromFlag) return fromFlag
|
|
69
|
+
const fromEnv = stripBearer(process.env.OSSY_API_KEY)
|
|
70
|
+
if (fromEnv) return fromEnv
|
|
71
|
+
const fromFile = stripBearer(readCredentials()?.token)
|
|
72
|
+
if (fromFile) return fromFile
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Order: explicit absolute flag > OSSY_API_URL env > saved credentials > default.
|
|
78
|
+
* Relative paths in the flag are ignored (matches existing publish behavior).
|
|
79
|
+
* @param {{ flag?: string }} [opts]
|
|
80
|
+
* @returns {string}
|
|
81
|
+
*/
|
|
82
|
+
export function getApiUrl ({ flag } = {}) {
|
|
83
|
+
if (flag && /^https?:\/\//i.test(String(flag).trim())) {
|
|
84
|
+
return String(flag).trim().replace(/\/$/, '')
|
|
85
|
+
}
|
|
86
|
+
const fromEnv = String(process.env.OSSY_API_URL ?? '').trim()
|
|
87
|
+
if (fromEnv) return fromEnv.replace(/\/$/, '')
|
|
88
|
+
const fromFile = String(readCredentials()?.apiUrl ?? '').trim()
|
|
89
|
+
if (/^https?:\/\//i.test(fromFile)) return fromFile.replace(/\/$/, '')
|
|
90
|
+
return DEFAULT_API_URL
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Order: explicit flag > OSSY_WORKSPACE_ID env > saved active workspace.
|
|
95
|
+
* Project-scoped commands (publish/cms) keep reading from src/config.js separately.
|
|
96
|
+
* @param {{ flag?: string }} [opts]
|
|
97
|
+
* @returns {string | null}
|
|
98
|
+
*/
|
|
99
|
+
export function getWorkspaceId ({ flag } = {}) {
|
|
100
|
+
const fromFlag = String(flag ?? '').trim()
|
|
101
|
+
if (fromFlag) return fromFlag
|
|
102
|
+
const fromEnv = String(process.env.OSSY_WORKSPACE_ID ?? '').trim()
|
|
103
|
+
if (fromEnv) return fromEnv
|
|
104
|
+
const fromFile = String(readConfig()?.workspaceId ?? '').trim()
|
|
105
|
+
if (fromFile) return fromFile
|
|
106
|
+
return null
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function describeStatePaths () {
|
|
110
|
+
return {
|
|
111
|
+
dir: configDir(),
|
|
112
|
+
credentials: credentialsPath(),
|
|
113
|
+
config: configPath(),
|
|
114
|
+
}
|
|
115
|
+
}
|