@sqaoss/flowy 1.7.0 → 1.8.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.
@@ -7,6 +7,15 @@ import {
7
7
  } from '../util/config.ts'
8
8
  import { resolveDescription } from '../util/description.ts'
9
9
  import { output, outputError } from '../util/format.ts'
10
+ import {
11
+ CREATE_EDGE,
12
+ CREATE_NODE,
13
+ DELETE_NODE,
14
+ DESCENDANTS,
15
+ DESCENDANTS_BRIEF,
16
+ GET_NODE,
17
+ UPDATE_NODE,
18
+ } from '../util/operations.ts'
10
19
 
11
20
  export const featureCommand = new Command('feature').description(
12
21
  'Manage features in the active project',
@@ -32,22 +41,15 @@ featureCommand
32
41
  descriptionFile: opts.descriptionFile,
33
42
  })
34
43
  const nodeData = await graphql<{ createNode: { id: string } }>(
35
- `mutation CreateNode($type: String!, $title: String!, $description: String) {
36
- createNode(type: $type, title: $title, description: $description) {
37
- id type title description status createdAt updatedAt
38
- }
39
- }`,
44
+ CREATE_NODE,
40
45
  { type: 'feature', title: opts.title, description },
41
46
  )
42
47
  const featureId = nodeData.createNode.id
43
- await graphql(
44
- `mutation CreateEdge($sourceId: String!, $targetId: String!, $relation: String!) {
45
- createEdge(sourceId: $sourceId, targetId: $targetId, relation: $relation) {
46
- sourceId targetId relation createdAt
47
- }
48
- }`,
49
- { sourceId: featureId, targetId: project.id, relation: 'part_of' },
50
- )
48
+ await graphql(CREATE_EDGE, {
49
+ sourceId: featureId,
50
+ targetId: project.id,
51
+ relation: 'part_of',
52
+ })
51
53
  output(nodeData.createNode)
52
54
  } catch (error) {
53
55
  outputError(error)
@@ -63,14 +65,11 @@ featureCommand
63
65
  const project = requireProject()
64
66
  const data = await graphql<{
65
67
  descendants: Array<{ id: string; type: string; title: string }>
66
- }>(
67
- `query Descendants($nodeId: String!, $relation: String, $maxDepth: Int) {
68
- descendants(nodeId: $nodeId, relation: $relation, maxDepth: $maxDepth) {
69
- id type title status
70
- }
71
- }`,
72
- { nodeId: project.id, relation: 'part_of', maxDepth: 1 },
73
- )
68
+ }>(DESCENDANTS_BRIEF, {
69
+ nodeId: project.id,
70
+ relation: 'part_of',
71
+ maxDepth: 1,
72
+ })
74
73
  const features = data.descendants.filter((n) => n.type === 'feature')
75
74
  const match = features.find(
76
75
  (f) => f.id === nameOrId || f.title === nameOrId,
@@ -111,14 +110,7 @@ featureCommand
111
110
  const project = requireProject()
112
111
  const data = await graphql<{
113
112
  descendants: Array<{ id: string; type: string }>
114
- }>(
115
- `query Descendants($nodeId: String!, $relation: String, $maxDepth: Int) {
116
- descendants(nodeId: $nodeId, relation: $relation, maxDepth: $maxDepth) {
117
- id type title description status createdAt updatedAt
118
- }
119
- }`,
120
- { nodeId: project.id, relation: 'part_of', maxDepth: 1 },
121
- )
113
+ }>(DESCENDANTS, { nodeId: project.id, relation: 'part_of', maxDepth: 1 })
122
114
  const features = data.descendants.filter((n) => n.type === 'feature')
123
115
  output(features)
124
116
  } catch (error) {
@@ -158,11 +150,7 @@ featureCommand
158
150
  }
159
151
  if (opts.metadata != null) variables.metadata = opts.metadata
160
152
  const data = await graphql<{ updateNode: unknown }>(
161
- `mutation UpdateNode($id: String!, $title: String, $description: String, $metadata: String) {
162
- updateNode(id: $id, title: $title, description: $description, metadata: $metadata) {
163
- id type title description status metadata createdAt updatedAt
164
- }
165
- }`,
153
+ UPDATE_NODE,
166
154
  variables,
167
155
  )
168
156
  output(data.updateNode)
@@ -183,12 +171,9 @@ featureCommand
183
171
  'No feature specified. Pass an ID or set an active feature.',
184
172
  )
185
173
  }
186
- const data = await graphql<{ deleteNode: boolean }>(
187
- `mutation DeleteNode($id: String!) {
188
- deleteNode(id: $id)
189
- }`,
190
- { id: featureId },
191
- )
174
+ const data = await graphql<{ deleteNode: boolean }>(DELETE_NODE, {
175
+ id: featureId,
176
+ })
192
177
  output({ deleted: data.deleteNode })
193
178
  } catch (error) {
194
179
  outputError(error)
@@ -207,14 +192,9 @@ featureCommand
207
192
  'No feature specified. Pass an ID or set an active feature.',
208
193
  )
209
194
  }
210
- const data = await graphql<{ node: unknown }>(
211
- `query GetNode($id: String!) {
212
- node(id: $id) {
213
- id type title description status metadata createdAt updatedAt
214
- }
215
- }`,
216
- { id: featureId },
217
- )
195
+ const data = await graphql<{ node: unknown }>(GET_NODE, {
196
+ id: featureId,
197
+ })
218
198
  output(data.node)
219
199
  } catch (error) {
220
200
  outputError(error)
@@ -9,6 +9,13 @@ import {
9
9
  parseManifest,
10
10
  readClientKey,
11
11
  } from '../util/manifest.ts'
12
+ import {
13
+ IMPORT_CREATE,
14
+ IMPORT_EDGE,
15
+ IMPORT_EDGES,
16
+ IMPORT_EXISTING,
17
+ IMPORT_UPDATE,
18
+ } from '../util/operations.ts'
12
19
 
13
20
  const NODE_TYPES = ['project', 'feature', 'task'] as const
14
21
 
@@ -53,12 +60,9 @@ function desiredEdges(manifest: Manifest): DesiredEdge[] {
53
60
  async function loadExisting(): Promise<Map<string, string>> {
54
61
  const idByKey = new Map<string, string>()
55
62
  for (const type of NODE_TYPES) {
56
- const data = await graphql<{ nodes: ServerNode[] }>(
57
- `query ImportExisting($type: String) {
58
- nodes(type: $type) { id type title metadata }
59
- }`,
60
- { type },
61
- )
63
+ const data = await graphql<{ nodes: ServerNode[] }>(IMPORT_EXISTING, {
64
+ type,
65
+ })
62
66
  for (const node of data.nodes) {
63
67
  const key = readClientKey(node.metadata)
64
68
  if (key) idByKey.set(key, node.id)
@@ -78,9 +82,7 @@ async function loadExistingEdges(nodeIds: string[]): Promise<Set<string>> {
78
82
  for (const nodeId of nodeIds) {
79
83
  for (const relation of RELATIONS) {
80
84
  const data = await graphql<{ edges: Array<{ id: string }> }>(
81
- `query ImportEdges($nodeId: String!, $relation: String!) {
82
- edges(nodeId: $nodeId, relation: $relation, direction: "outgoing") { id }
83
- }`,
85
+ IMPORT_EDGES,
84
86
  { nodeId, relation },
85
87
  )
86
88
  for (const target of data.edges) {
@@ -91,25 +93,13 @@ async function loadExistingEdges(nodeIds: string[]): Promise<Set<string>> {
91
93
  return existing
92
94
  }
93
95
 
94
- const CREATE_NODE = `mutation ImportCreate($type: String!, $title: String!, $description: String, $status: String, $metadata: String) {
95
- createNode(type: $type, title: $title, description: $description, status: $status, metadata: $metadata) { id }
96
- }`
97
-
98
- const UPDATE_NODE = `mutation ImportUpdate($id: String!, $title: String, $description: String, $status: String, $metadata: String) {
99
- updateNode(id: $id, title: $title, description: $description, status: $status, metadata: $metadata) { id }
100
- }`
101
-
102
- const CREATE_EDGE = `mutation ImportEdge($sourceId: String!, $targetId: String!, $relation: String!) {
103
- createEdge(sourceId: $sourceId, targetId: $targetId, relation: $relation) { sourceId targetId relation }
104
- }`
105
-
106
96
  async function upsertNode(
107
97
  node: ManifestNode,
108
98
  existingId: string | undefined,
109
99
  ): Promise<string> {
110
100
  const metadata = buildNodeMetadata(node.key, node.metadata)
111
101
  if (existingId) {
112
- await graphql<{ updateNode: { id: string } }>(UPDATE_NODE, {
102
+ await graphql<{ updateNode: { id: string } }>(IMPORT_UPDATE, {
113
103
  id: existingId,
114
104
  title: node.title,
115
105
  description: node.description ?? null,
@@ -118,7 +108,7 @@ async function upsertNode(
118
108
  })
119
109
  return existingId
120
110
  }
121
- const data = await graphql<{ createNode: { id: string } }>(CREATE_NODE, {
111
+ const data = await graphql<{ createNode: { id: string } }>(IMPORT_CREATE, {
122
112
  type: node.type,
123
113
  title: node.title,
124
114
  description: node.description ?? null,
@@ -162,7 +152,7 @@ export const importCommand = new Command('import')
162
152
  const targetId = idByKey.get(edge.targetKey)
163
153
  if (!sourceId || !targetId) continue
164
154
  if (present.has(edgeKey(sourceId, targetId, edge.relation))) continue
165
- await graphql(CREATE_EDGE, {
155
+ await graphql(IMPORT_EDGE, {
166
156
  sourceId,
167
157
  targetId,
168
158
  relation: edge.relation,
@@ -4,6 +4,7 @@ import { Command } from 'commander'
4
4
  import { graphql } from '../util/client.ts'
5
5
  import { loadConfig, saveConfig } from '../util/config.ts'
6
6
  import { output, outputError } from '../util/format.ts'
7
+ import { CREATE_PROJECT } from '../util/operations.ts'
7
8
 
8
9
  export const initCommand = new Command('init')
9
10
  .description('Initialize Flowy for the current git repository')
@@ -30,11 +31,7 @@ export const initCommand = new Command('init')
30
31
  }
31
32
 
32
33
  const data = await graphql<{ createNode: { id: string; title: string } }>(
33
- `mutation CreateProject($type: String!, $title: String!) {
34
- createNode(type: $type, title: $title) {
35
- id type title description status metadata createdAt updatedAt
36
- }
37
- }`,
34
+ CREATE_PROJECT,
38
35
  { type: 'project', title: repoName },
39
36
  )
40
37
 
@@ -7,7 +7,7 @@ let mockOutputError: ReturnType<typeof vi.fn>
7
7
 
8
8
  beforeEach(() => {
9
9
  mockLoadConfig = vi.fn(() => ({
10
- mode: 'saas',
10
+ mode: 'remote',
11
11
  apiUrl: 'https://flowy-ai.fly.dev/graphql',
12
12
  apiKey: 'old-key',
13
13
  client: { name: '' },
@@ -26,6 +26,16 @@ beforeEach(() => {
26
26
  loadConfig: mockLoadConfig,
27
27
  saveConfig: mockSaveConfig,
28
28
  fingerprintKey: actual.fingerprintKey,
29
+ requireRemoteMode: (commandName: string) => {
30
+ const cfg = (mockLoadConfig as unknown as () => { mode: string })()
31
+ if (cfg.mode === 'local') {
32
+ const err = new Error(
33
+ `"flowy ${commandName}" is only available in remote mode. The active mode is local mode.`,
34
+ ) as Error & { code?: string }
35
+ err.code = 'LOCAL_MODE'
36
+ throw err
37
+ }
38
+ },
29
39
  }
30
40
  })
31
41
 
@@ -160,4 +170,29 @@ describe('key command', () => {
160
170
  expect(mockSaveConfig).not.toHaveBeenCalled()
161
171
  expect(mockOutput).not.toHaveBeenCalled()
162
172
  })
173
+
174
+ test('rotate errors cleanly in local mode without hitting the server', async () => {
175
+ mockLoadConfig.mockReturnValue({
176
+ mode: 'local',
177
+ apiUrl: 'http://localhost:4000/graphql',
178
+ apiKey: 'old-key',
179
+ client: { name: '' },
180
+ projects: {},
181
+ })
182
+ const mockGraphql = vi.fn()
183
+ vi.doMock('../util/client.ts', () => ({ graphql: mockGraphql }))
184
+
185
+ const { keyCommand } = await import('./key.ts')
186
+ await keyCommand.parseAsync(['rotate'], { from: 'user' })
187
+
188
+ expect(mockGraphql).not.toHaveBeenCalled()
189
+ expect(mockSaveConfig).not.toHaveBeenCalled()
190
+ expect(mockOutput).not.toHaveBeenCalled()
191
+ expect(mockOutputError).toHaveBeenCalledWith(
192
+ expect.objectContaining({
193
+ message: expect.stringMatching(/local mode/i),
194
+ code: 'LOCAL_MODE',
195
+ }),
196
+ )
197
+ })
163
198
  })
@@ -1,7 +1,13 @@
1
1
  import { Command } from 'commander'
2
2
  import { graphql } from '../util/client.ts'
3
- import { fingerprintKey, loadConfig, saveConfig } from '../util/config.ts'
3
+ import {
4
+ fingerprintKey,
5
+ loadConfig,
6
+ requireRemoteMode,
7
+ saveConfig,
8
+ } from '../util/config.ts'
4
9
  import { output, outputError } from '../util/format.ts'
10
+ import { ROTATE_API_KEY } from '../util/operations.ts'
5
11
 
6
12
  export const keyCommand = new Command('key').description('API key management')
7
13
 
@@ -14,6 +20,7 @@ keyCommand
14
20
  )
15
21
  .action(async (opts) => {
16
22
  try {
23
+ requireRemoteMode('key rotate')
17
24
  const data = await graphql<{
18
25
  rotateApiKey: {
19
26
  user: {
@@ -25,14 +32,7 @@ keyCommand
25
32
  }
26
33
  apiKey: string
27
34
  }
28
- }>(
29
- `mutation RotateApiKey {
30
- rotateApiKey {
31
- user { id email tier createdAt graceEndsAt }
32
- apiKey
33
- }
34
- }`,
35
- )
35
+ }>(ROTATE_API_KEY)
36
36
 
37
37
  const { user, apiKey } = data.rotateApiKey
38
38
  const config = loadConfig()
@@ -3,6 +3,14 @@ import { graphql } from '../util/client.ts'
3
3
  import { loadConfig, requireProject, saveConfig } from '../util/config.ts'
4
4
  import { resolveDescription } from '../util/description.ts'
5
5
  import { output, outputError } from '../util/format.ts'
6
+ import {
7
+ CREATE_PROJECT,
8
+ DELETE_NODE,
9
+ GET_PROJECT,
10
+ LIST_PROJECTS,
11
+ LIST_PROJECTS_FOR_SET,
12
+ UPDATE_NODE,
13
+ } from '../util/operations.ts'
6
14
 
7
15
  export const projectCommand = new Command('project').description(
8
16
  'Manage projects',
@@ -14,14 +22,10 @@ projectCommand
14
22
  .argument('<name>', 'Project name')
15
23
  .action(async (name: string) => {
16
24
  try {
17
- const data = await graphql<{ createNode: unknown }>(
18
- `mutation CreateProject($type: String!, $title: String!) {
19
- createNode(type: $type, title: $title) {
20
- id type title description status metadata createdAt updatedAt
21
- }
22
- }`,
23
- { type: 'project', title: name },
24
- )
25
+ const data = await graphql<{ createNode: unknown }>(CREATE_PROJECT, {
26
+ type: 'project',
27
+ title: name,
28
+ })
25
29
  output(data.createNode)
26
30
  } catch (error) {
27
31
  outputError(error)
@@ -44,14 +48,7 @@ projectCommand
44
48
  try {
45
49
  const data = await graphql<{
46
50
  nodes: Array<{ id: string; title: string }>
47
- }>(
48
- `query ListProjects($type: String) {
49
- nodes(type: $type) {
50
- id title
51
- }
52
- }`,
53
- { type: 'project' },
54
- )
51
+ }>(LIST_PROJECTS_FOR_SET, { type: 'project' })
55
52
  const project = data.nodes.find((n) => n.title === name)
56
53
  if (!project) {
57
54
  throw new Error(`Project "${name}" not found.`)
@@ -67,14 +64,9 @@ projectCommand
67
64
  .description('List all projects')
68
65
  .action(async () => {
69
66
  try {
70
- const data = await graphql<{ nodes: unknown[] }>(
71
- `query ListProjects($type: String) {
72
- nodes(type: $type) {
73
- id type title description status createdAt updatedAt
74
- }
75
- }`,
76
- { type: 'project' },
77
- )
67
+ const data = await graphql<{ nodes: unknown[] }>(LIST_PROJECTS, {
68
+ type: 'project',
69
+ })
78
70
  output(data.nodes)
79
71
  } catch (error) {
80
72
  outputError(error)
@@ -84,14 +76,9 @@ projectCommand
84
76
  export async function showProject(id?: string): Promise<void> {
85
77
  try {
86
78
  const projectId = id ?? requireProject().id
87
- const data = await graphql<{ node: unknown }>(
88
- `query GetProject($id: String!) {
89
- node(id: $id) {
90
- id type title description status metadata createdAt updatedAt
91
- }
92
- }`,
93
- { id: projectId },
94
- )
79
+ const data = await graphql<{ node: unknown }>(GET_PROJECT, {
80
+ id: projectId,
81
+ })
95
82
  output(data.node)
96
83
  } catch (error) {
97
84
  outputError(error)
@@ -131,11 +118,7 @@ projectCommand
131
118
  }
132
119
  if (opts.metadata != null) variables.metadata = opts.metadata
133
120
  const data = await graphql<{ updateNode: unknown }>(
134
- `mutation UpdateNode($id: String!, $title: String, $description: String, $metadata: String) {
135
- updateNode(id: $id, title: $title, description: $description, metadata: $metadata) {
136
- id type title description status metadata createdAt updatedAt
137
- }
138
- }`,
121
+ UPDATE_NODE,
139
122
  variables,
140
123
  )
141
124
  output(data.updateNode)
@@ -151,12 +134,9 @@ projectCommand
151
134
  .action(async (id?: string) => {
152
135
  try {
153
136
  const projectId = id ?? requireProject().id
154
- const data = await graphql<{ deleteNode: boolean }>(
155
- `mutation DeleteNode($id: String!) {
156
- deleteNode(id: $id)
157
- }`,
158
- { id: projectId },
159
- )
137
+ const data = await graphql<{ deleteNode: boolean }>(DELETE_NODE, {
138
+ id: projectId,
139
+ })
160
140
  output({ deleted: data.deleteNode })
161
141
  } catch (error) {
162
142
  outputError(error)
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander'
2
2
  import { graphql } from '../util/client.ts'
3
3
  import { output, outputError } from '../util/format.ts'
4
+ import { SEARCH } from '../util/operations.ts'
4
5
 
5
6
  export const searchCommand = new Command('search')
6
7
  .description('Search nodes by text')
@@ -10,19 +11,12 @@ export const searchCommand = new Command('search')
10
11
  .option('--limit <n>', 'Limit results', '50')
11
12
  .action(async (query: string, opts) => {
12
13
  try {
13
- const data = await graphql<{ search: unknown[] }>(
14
- `query Search($query: String!, $type: String, $status: String, $limit: Int) {
15
- search(query: $query, type: $type, status: $status, limit: $limit) {
16
- id type title description status
17
- }
18
- }`,
19
- {
20
- query,
21
- type: opts.type,
22
- status: opts.status,
23
- limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
24
- },
25
- )
14
+ const data = await graphql<{ search: unknown[] }>(SEARCH, {
15
+ query,
16
+ type: opts.type,
17
+ status: opts.status,
18
+ limit: opts.limit ? Number.parseInt(opts.limit, 10) : undefined,
19
+ })
26
20
  output(data.search)
27
21
  } catch (error) {
28
22
  outputError(error)
@@ -8,7 +8,7 @@ let mockSpawnSync: ReturnType<typeof vi.fn>
8
8
 
9
9
  beforeEach(() => {
10
10
  mockLoadConfig = vi.fn(() => ({
11
- mode: 'saas',
11
+ mode: 'remote',
12
12
  apiUrl: 'https://flowy-ai.fly.dev/graphql',
13
13
  apiKey: '',
14
14
  client: { name: '' },
@@ -315,6 +315,53 @@ describe('setup command', () => {
315
315
  errSpy.mockRestore()
316
316
  })
317
317
 
318
+ test('setup remote persists the API key before any later step (save-after-register)', async () => {
319
+ const mockGraphql = vi.fn().mockResolvedValue({
320
+ register: {
321
+ user: {
322
+ id: 'user_1',
323
+ email: 'a@b.com',
324
+ tier: null,
325
+ createdAt: '2026-06-13T00:00:00Z',
326
+ graceEndsAt: null,
327
+ },
328
+ apiKey: 'flowy_key_persisted',
329
+ checkoutUrl: null,
330
+ },
331
+ })
332
+ vi.doMock('../util/client.ts', () => ({ graphql: mockGraphql }))
333
+
334
+ // The skill install (a step AFTER the key is obtained) blows up hard.
335
+ // The key must already be on disk by then so the user is never stranded.
336
+ let keySavedBeforeSkillInstall = false
337
+ mockSaveConfig.mockImplementation((cfg: { apiKey?: string }) => {
338
+ if (cfg.apiKey === 'flowy_key_persisted')
339
+ keySavedBeforeSkillInstall = true
340
+ })
341
+ mockSpawnSync.mockImplementation((cmd: string) => {
342
+ if (cmd === 'npx') {
343
+ // By the time the (post-register) skill install runs, the key is saved.
344
+ expect(keySavedBeforeSkillInstall).toBe(true)
345
+ throw new Error('npx exploded')
346
+ }
347
+ return { status: 0, stdout: Buffer.from('') }
348
+ })
349
+ const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
350
+
351
+ const { setupCommand } = await import('./setup.ts')
352
+ await setupCommand.parseAsync(['remote', '--email', 'a@b.com'], {
353
+ from: 'user',
354
+ })
355
+
356
+ // The key was saved at least once with the real value before the failure.
357
+ expect(
358
+ mockSaveConfig.mock.calls.some(
359
+ (c) => (c[0] as { apiKey?: string }).apiKey === 'flowy_key_persisted',
360
+ ),
361
+ ).toBe(true)
362
+ errSpy.mockRestore()
363
+ })
364
+
318
365
  test('setup remote warns when the skill install fails', async () => {
319
366
  const mockGraphql = vi.fn().mockResolvedValue({
320
367
  register: {
@@ -2,6 +2,7 @@ import { spawnSync } from 'node:child_process'
2
2
  import { Command, Option } from 'commander'
3
3
  import { fingerprintKey, loadConfig, saveConfig } from '../util/config.ts'
4
4
  import { output, outputError } from '../util/format.ts'
5
+ import { REGISTER } from '../util/operations.ts'
5
6
  import { pinnedInstallSpec } from './serve.ts'
6
7
 
7
8
  export const setupCommand = new Command('setup').description(
@@ -104,16 +105,7 @@ setupCommand
104
105
  apiKey: string
105
106
  checkoutUrl: string
106
107
  }
107
- }>(
108
- `mutation Register($email: String!, $tier: String) {
109
- register(email: $email, tier: $tier) {
110
- user { id email tier createdAt graceEndsAt }
111
- apiKey
112
- checkoutUrl
113
- }
114
- }`,
115
- { email: opts.email, tier: opts.tier },
116
- )
108
+ }>(REGISTER, { email: opts.email, tier: opts.tier })
117
109
 
118
110
  config.apiKey = data.register.apiKey
119
111
  saveConfig(config)
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander'
2
2
  import { graphql } from '../util/client.ts'
3
3
  import { output, outputError } from '../util/format.ts'
4
+ import { UPDATE_STATUS } from '../util/operations.ts'
4
5
 
5
6
  export const statusCommand = new Command('status')
6
7
  .description('Update a node status (shorthand)')
@@ -11,14 +12,10 @@ export const statusCommand = new Command('status')
11
12
  )
12
13
  .action(async (id: string, status: string) => {
13
14
  try {
14
- const data = await graphql<{ updateNode: unknown }>(
15
- `mutation UpdateStatus($id: String!, $status: String) {
16
- updateNode(id: $id, status: $status) {
17
- id type title status updatedAt
18
- }
19
- }`,
20
- { id, status },
21
- )
15
+ const data = await graphql<{ updateNode: unknown }>(UPDATE_STATUS, {
16
+ id,
17
+ status,
18
+ })
22
19
  output(data.updateNode)
23
20
  } catch (error) {
24
21
  outputError(error)