@stepzen/sdk 0.0.0-experimental-20221114-610843f6

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.
Files changed (113) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +30 -0
  3. package/lib/client-v2.d.ts +50 -0
  4. package/lib/client-v2.d.ts.map +1 -0
  5. package/lib/client-v2.js +91 -0
  6. package/lib/client-v2.js.map +1 -0
  7. package/lib/client.d.ts +33 -0
  8. package/lib/client.d.ts.map +1 -0
  9. package/lib/client.js +95 -0
  10. package/lib/client.js.map +1 -0
  11. package/lib/commands/account.d.ts +10 -0
  12. package/lib/commands/account.d.ts.map +1 -0
  13. package/lib/commands/account.js +39 -0
  14. package/lib/commands/account.js.map +1 -0
  15. package/lib/commands/authenticate.d.ts +4 -0
  16. package/lib/commands/authenticate.d.ts.map +1 -0
  17. package/lib/commands/authenticate.js +25 -0
  18. package/lib/commands/authenticate.js.map +1 -0
  19. package/lib/commands/deploy.d.ts +4 -0
  20. package/lib/commands/deploy.d.ts.map +1 -0
  21. package/lib/commands/deploy.js +46 -0
  22. package/lib/commands/deploy.js.map +1 -0
  23. package/lib/commands/getPublicAccount.d.ts +7 -0
  24. package/lib/commands/getPublicAccount.d.ts.map +1 -0
  25. package/lib/commands/getPublicAccount.js +59 -0
  26. package/lib/commands/getPublicAccount.js.map +1 -0
  27. package/lib/commands/list.d.ts +4 -0
  28. package/lib/commands/list.d.ts.map +1 -0
  29. package/lib/commands/list.js +29 -0
  30. package/lib/commands/list.js.map +1 -0
  31. package/lib/commands/upload.d.ts +4 -0
  32. package/lib/commands/upload.d.ts.map +1 -0
  33. package/lib/commands/upload.js +64 -0
  34. package/lib/commands/upload.js.map +1 -0
  35. package/lib/commands-v2/account.d.ts +9 -0
  36. package/lib/commands-v2/account.d.ts.map +1 -0
  37. package/lib/commands-v2/account.js +68 -0
  38. package/lib/commands-v2/account.js.map +1 -0
  39. package/lib/commands-v2/deploy.d.ts +15 -0
  40. package/lib/commands-v2/deploy.d.ts.map +1 -0
  41. package/lib/commands-v2/deploy.js +90 -0
  42. package/lib/commands-v2/deploy.js.map +1 -0
  43. package/lib/commands-v2/getPublicAccount.d.ts +8 -0
  44. package/lib/commands-v2/getPublicAccount.d.ts.map +1 -0
  45. package/lib/commands-v2/getPublicAccount.js +46 -0
  46. package/lib/commands-v2/getPublicAccount.js.map +1 -0
  47. package/lib/commands-v2/list.d.ts +12 -0
  48. package/lib/commands-v2/list.d.ts.map +1 -0
  49. package/lib/commands-v2/list.js +58 -0
  50. package/lib/commands-v2/list.js.map +1 -0
  51. package/lib/index.d.ts +12 -0
  52. package/lib/index.d.ts.map +1 -0
  53. package/lib/index.js +26 -0
  54. package/lib/index.js.map +1 -0
  55. package/lib/init-v2.d.ts +33 -0
  56. package/lib/init-v2.d.ts.map +1 -0
  57. package/lib/init-v2.js +33 -0
  58. package/lib/init-v2.js.map +1 -0
  59. package/lib/init.d.ts +33 -0
  60. package/lib/init.d.ts.map +1 -0
  61. package/lib/init.js +31 -0
  62. package/lib/init.js.map +1 -0
  63. package/lib/shared/constants.d.ts +7 -0
  64. package/lib/shared/constants.d.ts.map +1 -0
  65. package/lib/shared/constants.js +11 -0
  66. package/lib/shared/constants.js.map +1 -0
  67. package/lib/shared/graphql-client.d.ts +38 -0
  68. package/lib/shared/graphql-client.d.ts.map +1 -0
  69. package/lib/shared/graphql-client.js +80 -0
  70. package/lib/shared/graphql-client.js.map +1 -0
  71. package/lib/shared/payloads.d.ts +10 -0
  72. package/lib/shared/payloads.d.ts.map +1 -0
  73. package/lib/shared/payloads.js +123 -0
  74. package/lib/shared/payloads.js.map +1 -0
  75. package/lib/shared/request.d.ts +4 -0
  76. package/lib/shared/request.d.ts.map +1 -0
  77. package/lib/shared/request.js +25 -0
  78. package/lib/shared/request.js.map +1 -0
  79. package/lib/shared/transpiling.d.ts +15 -0
  80. package/lib/shared/transpiling.d.ts.map +1 -0
  81. package/lib/shared/transpiling.js +29 -0
  82. package/lib/shared/transpiling.js.map +1 -0
  83. package/lib/shared/types.d.ts +194 -0
  84. package/lib/shared/types.d.ts.map +1 -0
  85. package/lib/shared/types.js +4 -0
  86. package/lib/shared/types.js.map +1 -0
  87. package/lib/shared/validation.d.ts +3 -0
  88. package/lib/shared/validation.d.ts.map +1 -0
  89. package/lib/shared/validation.js +47 -0
  90. package/lib/shared/validation.js.map +1 -0
  91. package/package.json +52 -0
  92. package/src/client-v2.ts +120 -0
  93. package/src/client.ts +133 -0
  94. package/src/commands/account.ts +54 -0
  95. package/src/commands/authenticate.ts +30 -0
  96. package/src/commands/deploy.ts +72 -0
  97. package/src/commands/getPublicAccount.ts +77 -0
  98. package/src/commands/list.ts +50 -0
  99. package/src/commands/upload.ts +91 -0
  100. package/src/commands-v2/account.ts +98 -0
  101. package/src/commands-v2/deploy.ts +145 -0
  102. package/src/commands-v2/getPublicAccount.ts +67 -0
  103. package/src/commands-v2/list.ts +91 -0
  104. package/src/index.ts +27 -0
  105. package/src/init-v2.ts +44 -0
  106. package/src/init.ts +45 -0
  107. package/src/shared/constants.ts +9 -0
  108. package/src/shared/graphql-client.ts +136 -0
  109. package/src/shared/payloads.ts +153 -0
  110. package/src/shared/request.ts +28 -0
  111. package/src/shared/transpiling.ts +49 -0
  112. package/src/shared/types.ts +235 -0
  113. package/src/shared/validation.ts +52 -0
@@ -0,0 +1,153 @@
1
+ // Copyright (c) 2020,2021,2022, StepZen, Inc.
2
+
3
+ // This file contains helpers that zip a file or directory
4
+
5
+ import * as archiver from 'archiver'
6
+ import * as debug from 'debug'
7
+ import * as FormData from 'form-data'
8
+ import * as fs from 'fs'
9
+ import * as glob from 'glob'
10
+ import * as os from 'os'
11
+ import * as path from 'path'
12
+ import * as yaml from 'yaml'
13
+ import {transpileConfigurationset} from './transpiling'
14
+ import {validateConfigurationset} from './validation'
15
+ import {Configuration, SchemaFiles} from './types'
16
+
17
+ type YamlPayload = FormData
18
+ type ZipPayload = FormData
19
+
20
+ // This function takes a (yaml) file path
21
+ // and creates a FormData payload, containing the yaml content as 'yaml'
22
+ export const generateYamlPayload = async (
23
+ file: string | undefined,
24
+ ): Promise<YamlPayload> => {
25
+ return new Promise((resolve, reject) => {
26
+ const payload = new FormData()
27
+
28
+ if (file) {
29
+ if (!fs.existsSync(file)) {
30
+ reject(new Error(`File does not exist: ${file}`))
31
+ }
32
+ const content = fs.readFileSync(file, 'utf8')
33
+ debug('stepzen:sdk:payload')(`adding YAML payload\n${content}`)
34
+ payload.append('yaml', fs.readFileSync(file))
35
+ resolve(payload)
36
+ } else {
37
+ reject(new Error('File not specified'))
38
+ }
39
+ })
40
+ }
41
+
42
+ // This function takes a directory path, and optional data object,
43
+ // and creates a FormData payload, containing the directory contents as 'zip',
44
+ // and any other items in the data object.
45
+ export const generateZipPayload = async (
46
+ directory: string | undefined,
47
+ data: object | undefined,
48
+ filters: RegExp[],
49
+ ): Promise<ZipPayload> => {
50
+ return new Promise((resolve, reject) => {
51
+ const payload = new FormData()
52
+
53
+ if (data) {
54
+ for (const [key, value] of Object.entries(data)) {
55
+ payload.append(key, value)
56
+ }
57
+ }
58
+
59
+ // Store it in /tmp. Create a WriteStream
60
+ const filepath = path.join(os.tmpdir(), `stepzen-payload-${Date.now()}.zip`)
61
+ const output = fs.createWriteStream(filepath)
62
+
63
+ // We're making a zip file
64
+ const archive = archiver('zip', {
65
+ zlib: {level: 9},
66
+ })
67
+
68
+ // We're piping it to the WriteStream
69
+ archive.pipe(output)
70
+
71
+ // If we've specified a directory, add it to the archive
72
+ if (directory) {
73
+ if (!fs.existsSync(directory)) {
74
+ reject(new Error(`Directory does not exist: ${directory}`))
75
+ }
76
+
77
+ // Get all the files in the directory (and all subdirectories).
78
+ const allFiles = glob.sync('**', {cwd: directory})
79
+
80
+ // Loop through each file, because we want to filter them
81
+ // We add them manually, because when we use glob, it embeds
82
+ // a full path, and we want everything to explicitly
83
+ // start at the root of the archive.
84
+ allFiles.forEach(file => {
85
+ const include = filters.some(filter => file.match(filter))
86
+
87
+ if (include) {
88
+ debug('stepzen:archive')(file)
89
+ archive.file(path.join(directory, file), {name: file})
90
+ }
91
+ })
92
+ }
93
+
94
+ // Once we're done, append a ReadStream to the tmp file
95
+ output.on('close', () => {
96
+ const stream = fs.createReadStream(filepath)
97
+ payload.append('zip', stream)
98
+ // Remove the temporary zip
99
+ stream.on('close', () => {
100
+ fs.unlinkSync(filepath)
101
+ })
102
+ // This is where we return the payload
103
+ resolve(payload)
104
+ })
105
+
106
+ // Archive the file or directory
107
+ archive.finalize()
108
+ })
109
+ }
110
+
111
+ export const generateSchemaFilesPayload = (
112
+ workspaceFolderPath: string,
113
+ ): SchemaFiles => {
114
+ if (!fs.existsSync(workspaceFolderPath)) {
115
+ throw new Error(
116
+ `Failed to read folder ${workspaceFolderPath}. Does it exist?`,
117
+ )
118
+ }
119
+
120
+ const files = glob
121
+ .sync('**/*.graphql', {cwd: workspaceFolderPath})
122
+ .map(file => {
123
+ return {
124
+ name: file,
125
+ content: fs.readFileSync(`${workspaceFolderPath}/${file}`, 'utf8'),
126
+ }
127
+ })
128
+
129
+ if (files.length === 0) {
130
+ throw new Error(
131
+ `Failed to find any .graphql files in ${workspaceFolderPath}`,
132
+ )
133
+ }
134
+
135
+ return {
136
+ files,
137
+ entryPoint: 'index.graphql',
138
+ }
139
+ }
140
+
141
+ export const generateConfigurationPayload = async (
142
+ configFilePath?: string,
143
+ ): Promise<Configuration | null> => {
144
+ if (!configFilePath) {
145
+ return null
146
+ }
147
+
148
+ await validateConfigurationset(configFilePath)
149
+ const transpiled = await transpileConfigurationset(configFilePath)
150
+ debug('stepzen:sdk')(`effective config written to ${transpiled}`)
151
+ const content = fs.readFileSync(transpiled, 'utf-8')
152
+ return yaml.parse(content, {schema: 'failsafe'}) || null
153
+ }
@@ -0,0 +1,28 @@
1
+ // Copyright (c) 2020,2021,2022, StepZen, Inc.
2
+
3
+ import * as os from 'os'
4
+ import * as isWsl from 'is-wsl'
5
+ import {SDKConfiguration, StepZenAccount, ZenCtlRequestHeaders} from './types'
6
+
7
+ const {version} = require('../../package.json')
8
+
9
+ // mimics the logic from @oclif/config
10
+ // https://github.com/oclif/core/blob/d7067d13c7d80c9e0064455c27ac1ebb6ee53fd2/src/config/config.ts#L128
11
+ const arch = os.arch() === 'ia32' ? 'x86' : (os.arch() as any)
12
+ const platform = isWsl ? 'wsl' : (os.platform() as any)
13
+
14
+ export const getUserAgent = (sdkConfig: SDKConfiguration): string => {
15
+ return `${sdkConfig.appName} stepzen-sdk/${version} (${platform}; ${arch}; node-${process.version})`
16
+ }
17
+
18
+ export const getRequestHeaders = (
19
+ account: StepZenAccount,
20
+ sdkConfig: SDKConfiguration,
21
+ ): ZenCtlRequestHeaders => {
22
+ return {
23
+ authorization: `Apikey ${account.adminkey}`,
24
+ host: `${account.account}.${account.domain}`,
25
+ 'stepzen-cli-version': version,
26
+ 'user-agent': getUserAgent(sdkConfig),
27
+ }
28
+ }
@@ -0,0 +1,49 @@
1
+ // Copyright (c) 2020,2021,2022, StepZen, Inc.
2
+
3
+ import * as dotenv from 'dotenv'
4
+ import * as debug from 'debug'
5
+ import * as fs from 'fs-extra'
6
+ import * as os from 'os'
7
+ import * as path from 'path'
8
+ import {transpile} from '@stepzen/transpiler'
9
+
10
+ /**
11
+ * @returns `undefined` when called without arguments
12
+ */
13
+ export async function transpileConfigurationset(): Promise<undefined>
14
+
15
+ /**
16
+ * Strip off any unsupported properties and substitute any STEPZEN_*
17
+ * placeholders with matching environment variables (load a `.env` file
18
+ * if present next to the `config.yaml` file).
19
+ *
20
+ * @param file path to a `config.yaml` file
21
+ * @returns path to a temp copy of `config.yaml` file with all clean-up
22
+ * and substitutions made
23
+ */
24
+ export async function transpileConfigurationset(file: string): Promise<string>
25
+
26
+ export async function transpileConfigurationset(file?: string) {
27
+ if (!file) {
28
+ return
29
+ }
30
+
31
+ const source = file.substring(0, file.lastIndexOf('/'))
32
+ debug('stepzen:dotenv')(
33
+ `loading .env from ${path.resolve(source)}` +
34
+ `, at sdk/src/shared/transpiling.ts`,
35
+ )
36
+ dotenv.config({path: path.resolve(source, '.env')})
37
+
38
+ const tmp = path.join(os.tmpdir(), `stepzen-transpiler-${Date.now()}`)
39
+ const configPath = path.join(tmp, 'config.yaml')
40
+
41
+ fs.ensureDirSync(tmp)
42
+ fs.copyFileSync(file, configPath)
43
+
44
+ const result = await transpile(tmp)
45
+
46
+ fs.emptyDirSync(tmp)
47
+ fs.writeFileSync(configPath, result.config || '')
48
+ return configPath
49
+ }
@@ -0,0 +1,235 @@
1
+ // Copyright (c) 2020,2021,2022, StepZen, Inc.
2
+
3
+ export interface StepZenAccount {
4
+ account: string
5
+ adminkey: string
6
+
7
+ /**
8
+ * ZenCtl base URL, e.g. `https://fakefish.stepzen.io` for production,
9
+ * or `http://localhost` for local tests.
10
+ *
11
+ * Determined by taking the first option that is defined:
12
+ * - the `server` config property explicily passed into the `init()` function
13
+ * - the `STEPZEN_SERVER_URL` environment variable
14
+ * - the `https://{account}.stepzen.io` default value
15
+ *
16
+ * The `{account}` placeholder (if present) is replaced by the provided account
17
+ * name
18
+ */
19
+ server: string
20
+
21
+ /**
22
+ * ZenCtl domain, e.g. `stepzen.io`. This is only relevant for local testing
23
+ * when ZenCtl is running on localhost. In that case an additional `Host` HTTP
24
+ * header is added to requests, using a non-localhost host name constructed
25
+ * from this property.
26
+ *
27
+ * Determined by taking the first option that is defined:
28
+ * - the `domain` config property explicily passed into the `init()` function
29
+ * - the `STEPZEN_DOMAIN` environment variable
30
+ * - the `stepzen.io` default value
31
+ */
32
+ domain: string
33
+ }
34
+
35
+ export type StepZenCredentials = {
36
+ account: string
37
+ adminkey: string
38
+ apikey: string
39
+ }
40
+
41
+ export type UserCredentialsClientOptions = Pick<
42
+ StepZenAccount,
43
+ 'account' | 'adminkey'
44
+ > &
45
+ Partial<StepZenAccount>
46
+
47
+ export type AnonymousClientOptions = {
48
+ publicAccountToken: string
49
+ server?: StepZenAccount['server']
50
+ domain?: StepZenAccount['domain']
51
+ }
52
+
53
+ export interface StepZenDeploy {
54
+ configurationsets?: string[]
55
+ destination: string
56
+ schema: string
57
+ }
58
+
59
+ export interface StepZenList {
60
+ type: 'deployments' | 'schemas' | 'configurationsets'
61
+ }
62
+
63
+ export interface StepZenUpload {
64
+ destination: string
65
+ directory?: string
66
+ file?: string
67
+ type: string
68
+ }
69
+
70
+ export interface ZenCtlRequestHeaders {
71
+ authorization: string
72
+ host: string
73
+ 'stepzen-cli-version': string
74
+ 'user-agent': string
75
+ }
76
+
77
+ export interface ZenCtlResponse {
78
+ errors?: Array<string>
79
+ message?: string
80
+ results?: Array<string>
81
+ success: boolean
82
+ }
83
+
84
+ export interface SDKConfiguration {
85
+ /**
86
+ * The name and version of that app that uses the SDK,
87
+ * e.g. `stepzen-cli/0.9.32`
88
+ *
89
+ * It is appended to the user-agent string in all requests made to StepZen
90
+ * through the SDK and becomes availabe in the log analytics.
91
+ */
92
+ appName: string
93
+ }
94
+
95
+ export interface SDKConfigurationV2 extends SDKConfiguration {
96
+ /**
97
+ * The name and version of that app that uses the SDK,
98
+ * e.g. `stepzen-cli/0.9.32`
99
+ *
100
+ * It is appended to the user-agent string in all requests made to StepZen
101
+ * through the SDK and becomes availabe in the log analytics.
102
+ */
103
+ appName: string
104
+
105
+ /**
106
+ * Version of ZenCtl API to use:
107
+ * - `undefined`, `v1`: implies the v1 REST API at /ctl/admin/
108
+ * - `v2`: implies the v2 GraphQL API
109
+ */
110
+ apiVersion: string
111
+
112
+ /**
113
+ * URL of the ZenCtl GraphQL API,
114
+ * e.g. https://stepzen.stepzen.net/api/zenctl2/__graphql
115
+ */
116
+ zenctlApiUrl: string
117
+
118
+ /**
119
+ * URL of the StepZen getPublicAccount GraphQL API,
120
+ * e.g. https://stepzen.stepzen.net/api/publicaccount/__graphql
121
+ */
122
+ publicAccountApiUrl: string
123
+ }
124
+
125
+ export interface ZenCtlSuccessResponseV2<T> {
126
+ data: T
127
+ error: undefined
128
+ }
129
+
130
+ export interface ZenCtlErrorResponseV2 {
131
+ data: undefined
132
+ error: {
133
+ message: string
134
+ }
135
+ }
136
+
137
+ export type ZenCtlResponseV2<T> =
138
+ | ZenCtlSuccessResponseV2<T>
139
+ | ZenCtlErrorResponseV2
140
+
141
+ export interface StepZenCredentialsV2 {
142
+ account: string
143
+ adminKey: string
144
+ apiKeys: string[]
145
+ deploymentType: string
146
+ }
147
+
148
+ export interface StepZenAccountV2 {
149
+ account: string
150
+ ownerEmail: string
151
+ adminKey: string
152
+ apiKeys: string[]
153
+ deploymentType: string
154
+ }
155
+
156
+ export interface StepZenEndpointV2 {
157
+ account: string
158
+ deploymentType: string
159
+ folderName: string
160
+ endpointName: string
161
+ public: boolean
162
+ endpointType: string
163
+ }
164
+
165
+ export interface SchemaFiles {
166
+ files: SchemaFile[]
167
+ entryPoint?: String
168
+ }
169
+
170
+ export interface SchemaFile {
171
+ name: string
172
+ content: string
173
+ }
174
+
175
+ export interface Configuration {
176
+ configurationset?: ConfigurationSet[]
177
+ ruleset?: QueryRule[]
178
+ access?: Policies
179
+ deployment?: DeploymentSettings
180
+ }
181
+
182
+ export interface ConfigurationSet {
183
+ configuration?: Record<string, any>
184
+ }
185
+
186
+ export interface QueryRule {
187
+ query: string
188
+ disabled?: boolean
189
+ rules?: Rule[]
190
+ }
191
+
192
+ export interface Rule {
193
+ predicate: string
194
+ action: string
195
+ }
196
+
197
+ export interface Policies {
198
+ policies?: Policy[]
199
+ }
200
+
201
+ export interface Policy {
202
+ type?: string
203
+ rules?: FieldRule[]
204
+ policyDefault?: FieldCondition
205
+ }
206
+
207
+ export interface FieldRule {
208
+ name?: string
209
+ description?: string
210
+ condition: string
211
+ fields?: string[]
212
+ }
213
+
214
+ export interface FieldCondition {
215
+ condition: string
216
+ }
217
+
218
+ export interface DeploymentSettings {
219
+ identity?: IdentitySettings
220
+ }
221
+
222
+ export interface IdentitySettings {
223
+ keys?: JWTKey[]
224
+ issuer?: string
225
+ subject?: string
226
+ audience?: string
227
+ claims?: string[]
228
+ jwksendpoint?: string
229
+ }
230
+
231
+ export interface JWTKey {
232
+ algorithm?: string
233
+ keyid?: string
234
+ key?: string
235
+ }
@@ -0,0 +1,52 @@
1
+ // Copyright (c) 2020,2021,2022, StepZen, Inc.
2
+
3
+ import * as debug from 'debug'
4
+ import * as fs from 'fs'
5
+ import * as glob from 'glob'
6
+ import * as yaml from 'yaml'
7
+
8
+ // Validate the Configurationset file
9
+ export const validateConfigurationset = async (file: string | undefined) => {
10
+ if (!file) {
11
+ debug('stepzen:sdk')(
12
+ `validateConfigurationset() expected 'file' to be a` +
13
+ ` non-empty string but got ${file}`,
14
+ )
15
+ throw new Error('You must provide a file path')
16
+ }
17
+
18
+ if (!fs.existsSync(file)) {
19
+ debug('stepzen:sdk')(`validateConfigurationset() expected ${file} to exist`)
20
+ throw new Error('The file does not exist')
21
+ }
22
+
23
+ const content = fs.readFileSync(file, 'utf8')
24
+
25
+ // Ensure the file is valid YAML
26
+ try {
27
+ yaml.parse(content)
28
+ } catch (error) {
29
+ debug('stepzen:sdk')(
30
+ `validateConfigurationset() expected ${file} to parse as YAML,` +
31
+ ` but got an error ${error}`,
32
+ )
33
+ throw new Error('The file is not valid YAML')
34
+ }
35
+ }
36
+
37
+ // Validate the Schema directory
38
+ export const validateSchema = async (directory: string | undefined) => {
39
+ if (!directory) {
40
+ throw new Error('You must provide a directory path')
41
+ }
42
+
43
+ if (!fs.existsSync(directory)) {
44
+ throw new Error('The directory does not exist')
45
+ }
46
+
47
+ // Ensure there's a root `index.graphql` file
48
+ const allSchemaFiles = glob.sync('index.graphql', {cwd: directory})
49
+ if (allSchemaFiles.length === 0) {
50
+ throw new Error('Schemas must include an `index.graphql` file')
51
+ }
52
+ }