@positronic/cli 0.0.2

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 (193) hide show
  1. package/dist/src/cli.js +739 -0
  2. package/dist/src/commands/backend.js +199 -0
  3. package/dist/src/commands/brain.js +446 -0
  4. package/dist/src/commands/brain.test.js +2936 -0
  5. package/dist/src/commands/helpers.js +1315 -0
  6. package/dist/src/commands/helpers.test.js +832 -0
  7. package/dist/src/commands/project-config-manager.js +197 -0
  8. package/dist/src/commands/project.js +130 -0
  9. package/dist/src/commands/project.test.js +1201 -0
  10. package/dist/src/commands/resources.js +272 -0
  11. package/dist/src/commands/resources.test.js +2511 -0
  12. package/dist/src/commands/schedule.js +73 -0
  13. package/dist/src/commands/schedule.test.js +1235 -0
  14. package/dist/src/commands/secret.js +87 -0
  15. package/dist/src/commands/secret.test.d.js +1 -0
  16. package/dist/src/commands/secret.test.js +761 -0
  17. package/dist/src/commands/server.js +816 -0
  18. package/dist/src/commands/server.test.js +1237 -0
  19. package/dist/src/commands/test-utils.js +737 -0
  20. package/dist/src/components/brain-history.js +169 -0
  21. package/dist/src/components/brain-list.js +108 -0
  22. package/dist/src/components/brain-rerun.js +313 -0
  23. package/dist/src/components/brain-show.js +65 -0
  24. package/dist/src/components/error.js +19 -0
  25. package/dist/src/components/project-add.js +95 -0
  26. package/dist/src/components/project-create.js +276 -0
  27. package/dist/src/components/project-list.js +88 -0
  28. package/dist/src/components/project-remove.js +91 -0
  29. package/dist/src/components/project-select.js +224 -0
  30. package/dist/src/components/project-show.js +41 -0
  31. package/dist/src/components/resource-clear.js +152 -0
  32. package/dist/src/components/resource-delete.js +189 -0
  33. package/dist/src/components/resource-list.js +174 -0
  34. package/dist/src/components/resource-sync.js +386 -0
  35. package/dist/src/components/resource-types.js +243 -0
  36. package/dist/src/components/resource-upload.js +366 -0
  37. package/dist/src/components/schedule-create.js +259 -0
  38. package/dist/src/components/schedule-delete.js +161 -0
  39. package/dist/src/components/schedule-list.js +176 -0
  40. package/dist/src/components/schedule-runs.js +103 -0
  41. package/dist/src/components/secret-bulk.js +262 -0
  42. package/dist/src/components/secret-create.js +199 -0
  43. package/dist/src/components/secret-delete.js +190 -0
  44. package/dist/src/components/secret-list.js +190 -0
  45. package/dist/src/components/secret-sync.js +303 -0
  46. package/dist/src/components/watch.js +184 -0
  47. package/dist/src/hooks/useApi.js +512 -0
  48. package/dist/src/positronic.js +33 -0
  49. package/dist/src/test/mock-api-client.js +371 -0
  50. package/dist/src/test/test-dev-server.js +1376 -0
  51. package/dist/types/cli.d.ts +9 -0
  52. package/dist/types/cli.d.ts.map +1 -0
  53. package/dist/types/commands/backend.d.ts +6 -0
  54. package/dist/types/commands/backend.d.ts.map +1 -0
  55. package/dist/types/commands/brain.d.ts +35 -0
  56. package/dist/types/commands/brain.d.ts.map +1 -0
  57. package/dist/types/commands/helpers.d.ts +55 -0
  58. package/dist/types/commands/helpers.d.ts.map +1 -0
  59. package/dist/types/commands/project-config-manager.d.ts +37 -0
  60. package/dist/types/commands/project-config-manager.d.ts.map +1 -0
  61. package/dist/types/commands/project.d.ts +55 -0
  62. package/dist/types/commands/project.d.ts.map +1 -0
  63. package/dist/types/commands/resources.d.ts +13 -0
  64. package/dist/types/commands/resources.d.ts.map +1 -0
  65. package/dist/types/commands/schedule.d.ts +27 -0
  66. package/dist/types/commands/schedule.d.ts.map +1 -0
  67. package/dist/types/commands/secret.d.ts +23 -0
  68. package/dist/types/commands/secret.d.ts.map +1 -0
  69. package/dist/types/commands/server.d.ts +12 -0
  70. package/dist/types/commands/server.d.ts.map +1 -0
  71. package/dist/types/commands/test-utils.d.ts +45 -0
  72. package/dist/types/commands/test-utils.d.ts.map +1 -0
  73. package/dist/types/components/brain-history.d.ts +7 -0
  74. package/dist/types/components/brain-history.d.ts.map +1 -0
  75. package/dist/types/components/brain-list.d.ts +2 -0
  76. package/dist/types/components/brain-list.d.ts.map +1 -0
  77. package/dist/types/components/brain-rerun.d.ts +9 -0
  78. package/dist/types/components/brain-rerun.d.ts.map +1 -0
  79. package/dist/types/components/brain-show.d.ts +6 -0
  80. package/dist/types/components/brain-show.d.ts.map +1 -0
  81. package/dist/types/components/error.d.ts +10 -0
  82. package/dist/types/components/error.d.ts.map +1 -0
  83. package/dist/types/components/project-add.d.ts +9 -0
  84. package/dist/types/components/project-add.d.ts.map +1 -0
  85. package/dist/types/components/project-create.d.ts +6 -0
  86. package/dist/types/components/project-create.d.ts.map +1 -0
  87. package/dist/types/components/project-list.d.ts +7 -0
  88. package/dist/types/components/project-list.d.ts.map +1 -0
  89. package/dist/types/components/project-remove.d.ts +8 -0
  90. package/dist/types/components/project-remove.d.ts.map +1 -0
  91. package/dist/types/components/project-select.d.ts +8 -0
  92. package/dist/types/components/project-select.d.ts.map +1 -0
  93. package/dist/types/components/project-show.d.ts +7 -0
  94. package/dist/types/components/project-show.d.ts.map +1 -0
  95. package/dist/types/components/resource-clear.d.ts +2 -0
  96. package/dist/types/components/resource-clear.d.ts.map +1 -0
  97. package/dist/types/components/resource-delete.d.ts +9 -0
  98. package/dist/types/components/resource-delete.d.ts.map +1 -0
  99. package/dist/types/components/resource-list.d.ts +2 -0
  100. package/dist/types/components/resource-list.d.ts.map +1 -0
  101. package/dist/types/components/resource-sync.d.ts +8 -0
  102. package/dist/types/components/resource-sync.d.ts.map +1 -0
  103. package/dist/types/components/resource-types.d.ts +7 -0
  104. package/dist/types/components/resource-types.d.ts.map +1 -0
  105. package/dist/types/components/resource-upload.d.ts +8 -0
  106. package/dist/types/components/resource-upload.d.ts.map +1 -0
  107. package/dist/types/components/schedule-create.d.ts +7 -0
  108. package/dist/types/components/schedule-create.d.ts.map +1 -0
  109. package/dist/types/components/schedule-delete.d.ts +7 -0
  110. package/dist/types/components/schedule-delete.d.ts.map +1 -0
  111. package/dist/types/components/schedule-list.d.ts +6 -0
  112. package/dist/types/components/schedule-list.d.ts.map +1 -0
  113. package/dist/types/components/schedule-runs.d.ts +8 -0
  114. package/dist/types/components/schedule-runs.d.ts.map +1 -0
  115. package/dist/types/components/secret-bulk.d.ts +8 -0
  116. package/dist/types/components/secret-bulk.d.ts.map +1 -0
  117. package/dist/types/components/secret-create.d.ts +9 -0
  118. package/dist/types/components/secret-create.d.ts.map +1 -0
  119. package/dist/types/components/secret-delete.d.ts +8 -0
  120. package/dist/types/components/secret-delete.d.ts.map +1 -0
  121. package/dist/types/components/secret-list.d.ts +7 -0
  122. package/dist/types/components/secret-list.d.ts.map +1 -0
  123. package/dist/types/components/secret-sync.d.ts +9 -0
  124. package/dist/types/components/secret-sync.d.ts.map +1 -0
  125. package/dist/types/components/watch.d.ts +7 -0
  126. package/dist/types/components/watch.d.ts.map +1 -0
  127. package/dist/types/hooks/useApi.d.ts +29 -0
  128. package/dist/types/hooks/useApi.d.ts.map +1 -0
  129. package/dist/types/positronic.d.ts +3 -0
  130. package/dist/types/positronic.d.ts.map +1 -0
  131. package/dist/types/test/mock-api-client.d.ts +25 -0
  132. package/dist/types/test/mock-api-client.d.ts.map +1 -0
  133. package/dist/types/test/test-dev-server.d.ts +129 -0
  134. package/dist/types/test/test-dev-server.d.ts.map +1 -0
  135. package/package.json +37 -0
  136. package/src/cli.ts +981 -0
  137. package/src/commands/backend.ts +63 -0
  138. package/src/commands/brain.test.ts +1004 -0
  139. package/src/commands/brain.ts +215 -0
  140. package/src/commands/helpers.test.ts +487 -0
  141. package/src/commands/helpers.ts +870 -0
  142. package/src/commands/project-config-manager.ts +152 -0
  143. package/src/commands/project.test.ts +502 -0
  144. package/src/commands/project.ts +109 -0
  145. package/src/commands/resources.test.ts +1052 -0
  146. package/src/commands/resources.ts +97 -0
  147. package/src/commands/schedule.test.ts +481 -0
  148. package/src/commands/schedule.ts +65 -0
  149. package/src/commands/secret.test.ts +210 -0
  150. package/src/commands/secret.ts +50 -0
  151. package/src/commands/server.test.ts +493 -0
  152. package/src/commands/server.ts +353 -0
  153. package/src/commands/test-utils.ts +324 -0
  154. package/src/components/brain-history.tsx +198 -0
  155. package/src/components/brain-list.tsx +105 -0
  156. package/src/components/brain-rerun.tsx +111 -0
  157. package/src/components/brain-show.tsx +92 -0
  158. package/src/components/error.tsx +24 -0
  159. package/src/components/project-add.tsx +59 -0
  160. package/src/components/project-create.tsx +83 -0
  161. package/src/components/project-list.tsx +83 -0
  162. package/src/components/project-remove.tsx +55 -0
  163. package/src/components/project-select.tsx +200 -0
  164. package/src/components/project-show.tsx +58 -0
  165. package/src/components/resource-clear.tsx +127 -0
  166. package/src/components/resource-delete.tsx +160 -0
  167. package/src/components/resource-list.tsx +177 -0
  168. package/src/components/resource-sync.tsx +170 -0
  169. package/src/components/resource-types.tsx +55 -0
  170. package/src/components/resource-upload.tsx +182 -0
  171. package/src/components/schedule-create.tsx +90 -0
  172. package/src/components/schedule-delete.tsx +116 -0
  173. package/src/components/schedule-list.tsx +186 -0
  174. package/src/components/schedule-runs.tsx +151 -0
  175. package/src/components/secret-bulk.tsx +79 -0
  176. package/src/components/secret-create.tsx +49 -0
  177. package/src/components/secret-delete.tsx +41 -0
  178. package/src/components/secret-list.tsx +41 -0
  179. package/src/components/watch.tsx +155 -0
  180. package/src/hooks/useApi.ts +183 -0
  181. package/src/positronic.ts +40 -0
  182. package/src/test/data/resources/config.json +1 -0
  183. package/src/test/data/resources/data/config.json +1 -0
  184. package/src/test/data/resources/data/logo.png +2 -0
  185. package/src/test/data/resources/docs/api.md +3 -0
  186. package/src/test/data/resources/docs/readme.md +3 -0
  187. package/src/test/data/resources/example.md +3 -0
  188. package/src/test/data/resources/file with spaces.txt +1 -0
  189. package/src/test/data/resources/readme.md +3 -0
  190. package/src/test/data/resources/test.txt +1 -0
  191. package/src/test/mock-api-client.ts +145 -0
  192. package/src/test/test-dev-server.ts +1003 -0
  193. package/tsconfig.json +11 -0
@@ -0,0 +1,1201 @@
1
+ /**
2
+ * CLI Integration Tests - Testing Philosophy
3
+ *
4
+ * These tests are designed to be maintainable and resilient to UI changes.
5
+ *
6
+ * Key principles:
7
+ * 1. **Test behavior, not formatting** - Don't check for specific icons, emojis, or exact formatting
8
+ * 2. **Look for essential keywords only** - Focus on the minimum text that indicates success/failure
9
+ * 3. **Use simple assertions** - Prefer toContain() over complex regex when possible
10
+ * 4. **Be case-insensitive** - Use toLowerCase() to avoid breaking on capitalization changes
11
+ *
12
+ * Examples:
13
+ * - For success: look for "added", "switched", etc. (not specific success emojis)
14
+ * - For errors: look for "not found", "invalid", "already exists" (not error icons)
15
+ * - For data: check that project names and URLs appear, but not their exact formatting
16
+ *
17
+ * This approach ensures tests remain stable as the CLI's output formatting evolves,
18
+ * while still verifying that core functionality works correctly.
19
+ */ // Import nock to use in tests, but configuration happens in jest.setup.js
20
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
21
+ try {
22
+ var info = gen[key](arg);
23
+ var value = info.value;
24
+ } catch (error) {
25
+ reject(error);
26
+ return;
27
+ }
28
+ if (info.done) {
29
+ resolve(value);
30
+ } else {
31
+ Promise.resolve(value).then(_next, _throw);
32
+ }
33
+ }
34
+ function _async_to_generator(fn) {
35
+ return function() {
36
+ var self = this, args = arguments;
37
+ return new Promise(function(resolve, reject) {
38
+ var gen = fn.apply(self, args);
39
+ function _next(value) {
40
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
41
+ }
42
+ function _throw(err) {
43
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
44
+ }
45
+ _next(undefined);
46
+ });
47
+ };
48
+ }
49
+ function _ts_generator(thisArg, body) {
50
+ var f, y, t, _ = {
51
+ label: 0,
52
+ sent: function() {
53
+ if (t[0] & 1) throw t[1];
54
+ return t[1];
55
+ },
56
+ trys: [],
57
+ ops: []
58
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
59
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
60
+ return this;
61
+ }), g;
62
+ function verb(n) {
63
+ return function(v) {
64
+ return step([
65
+ n,
66
+ v
67
+ ]);
68
+ };
69
+ }
70
+ function step(op) {
71
+ if (f) throw new TypeError("Generator is already executing.");
72
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
73
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
74
+ if (y = 0, t) op = [
75
+ op[0] & 2,
76
+ t.value
77
+ ];
78
+ switch(op[0]){
79
+ case 0:
80
+ case 1:
81
+ t = op;
82
+ break;
83
+ case 4:
84
+ _.label++;
85
+ return {
86
+ value: op[1],
87
+ done: false
88
+ };
89
+ case 5:
90
+ _.label++;
91
+ y = op[1];
92
+ op = [
93
+ 0
94
+ ];
95
+ continue;
96
+ case 7:
97
+ op = _.ops.pop();
98
+ _.trys.pop();
99
+ continue;
100
+ default:
101
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
102
+ _ = 0;
103
+ continue;
104
+ }
105
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
106
+ _.label = op[1];
107
+ break;
108
+ }
109
+ if (op[0] === 6 && _.label < t[1]) {
110
+ _.label = t[1];
111
+ t = op;
112
+ break;
113
+ }
114
+ if (t && _.label < t[2]) {
115
+ _.label = t[2];
116
+ _.ops.push(op);
117
+ break;
118
+ }
119
+ if (t[2]) _.ops.pop();
120
+ _.trys.pop();
121
+ continue;
122
+ }
123
+ op = body.call(thisArg, _);
124
+ } catch (e) {
125
+ op = [
126
+ 6,
127
+ e
128
+ ];
129
+ y = 0;
130
+ } finally{
131
+ f = t = 0;
132
+ }
133
+ if (op[0] & 5) throw op[1];
134
+ return {
135
+ value: op[0] ? op[1] : void 0,
136
+ done: true
137
+ };
138
+ }
139
+ }
140
+ import * as fs from 'fs';
141
+ import * as path from 'path';
142
+ import * as os from 'os';
143
+ import { describe, it, expect, afterEach } from '@jest/globals';
144
+ import { createTestEnv, px } from './test-utils.js';
145
+ describe('CLI Integration: positronic server with project', function() {
146
+ it('runs a brain', function() {
147
+ return _async_to_generator(function() {
148
+ var env, px, waitForOutput, outputContainsRunId, outputContainsRunPrefix;
149
+ return _ts_generator(this, function(_state) {
150
+ switch(_state.label){
151
+ case 0:
152
+ return [
153
+ 4,
154
+ createTestEnv()
155
+ ];
156
+ case 1:
157
+ env = _state.sent();
158
+ // Setup test brain
159
+ env.setup(function(dir) {
160
+ var brainsDir = path.join(dir, 'brains');
161
+ fs.mkdirSync(brainsDir, {
162
+ recursive: true
163
+ });
164
+ // Create a simple test brain
165
+ fs.writeFileSync(path.join(brainsDir, 'test-brain.ts'), "\n export default function testBrain() {\n return {\n title: 'Test Brain',\n steps: [\n {\n title: 'Test Step',\n run: async () => {\n return { success: true };\n }\n }\n ]\n };\n }\n ");
166
+ });
167
+ return [
168
+ 4,
169
+ env.start()
170
+ ];
171
+ case 2:
172
+ px = _state.sent();
173
+ _state.label = 3;
174
+ case 3:
175
+ _state.trys.push([
176
+ 3,
177
+ ,
178
+ 7,
179
+ 9
180
+ ]);
181
+ return [
182
+ 4,
183
+ px([
184
+ 'run',
185
+ 'test-brain'
186
+ ])
187
+ ];
188
+ case 4:
189
+ waitForOutput = _state.sent().waitForOutput;
190
+ return [
191
+ 4,
192
+ waitForOutput(/Run ID:/)
193
+ ];
194
+ case 5:
195
+ outputContainsRunId = _state.sent();
196
+ return [
197
+ 4,
198
+ waitForOutput(/run-/)
199
+ ];
200
+ case 6:
201
+ outputContainsRunPrefix = _state.sent();
202
+ expect(outputContainsRunId).toBe(true);
203
+ expect(outputContainsRunPrefix).toBe(true);
204
+ return [
205
+ 3,
206
+ 9
207
+ ];
208
+ case 7:
209
+ return [
210
+ 4,
211
+ env.stopAndCleanup()
212
+ ];
213
+ case 8:
214
+ _state.sent();
215
+ return [
216
+ 7
217
+ ];
218
+ case 9:
219
+ return [
220
+ 2
221
+ ];
222
+ }
223
+ });
224
+ })();
225
+ });
226
+ });
227
+ describe('CLI Integration: project commands', function() {
228
+ var tempDir;
229
+ var configDir;
230
+ beforeEach(function() {
231
+ // Create a temp directory for testing
232
+ tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'positronic-project-test-'));
233
+ configDir = path.join(tempDir, '.positronic');
234
+ });
235
+ afterEach(function() {
236
+ // Clean up test directory
237
+ fs.rmSync(tempDir, {
238
+ recursive: true,
239
+ force: true
240
+ });
241
+ });
242
+ describe('project add', function() {
243
+ it('should add a new project successfully', function() {
244
+ return _async_to_generator(function() {
245
+ var _ref, waitForOutput, instance, isReady, output, configPath, config;
246
+ return _ts_generator(this, function(_state) {
247
+ switch(_state.label){
248
+ case 0:
249
+ return [
250
+ 4,
251
+ px([
252
+ 'project',
253
+ 'add',
254
+ 'My App',
255
+ '--url',
256
+ 'https://my-app.positronic.sh'
257
+ ], {
258
+ configDir: configDir
259
+ })
260
+ ];
261
+ case 1:
262
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
263
+ return [
264
+ 4,
265
+ waitForOutput(/added/i)
266
+ ];
267
+ case 2:
268
+ isReady = _state.sent();
269
+ expect(isReady).toBe(true);
270
+ output = instance.lastFrame();
271
+ expect(output).toMatch(/my app/i);
272
+ expect(output).toContain('https://my-app.positronic.sh');
273
+ // Verify config file was created
274
+ configPath = path.join(configDir, 'config.json');
275
+ expect(fs.existsSync(configPath)).toBe(true);
276
+ config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
277
+ expect(config.projects).toHaveLength(1);
278
+ expect(config.projects[0].name).toBe('My App');
279
+ expect(config.currentProject).toBe('My App');
280
+ return [
281
+ 2
282
+ ];
283
+ }
284
+ });
285
+ })();
286
+ });
287
+ it('should reject duplicate project names', function() {
288
+ return _async_to_generator(function() {
289
+ var addFirstProject, addDuplicateProject;
290
+ return _ts_generator(this, function(_state) {
291
+ switch(_state.label){
292
+ case 0:
293
+ return [
294
+ 4,
295
+ px([
296
+ 'project',
297
+ 'add',
298
+ 'My App',
299
+ '--url',
300
+ 'https://my-app.positronic.sh'
301
+ ], {
302
+ configDir: configDir
303
+ })
304
+ ];
305
+ case 1:
306
+ addFirstProject = _state.sent();
307
+ return [
308
+ 4,
309
+ addFirstProject.waitForOutput(/added/i)
310
+ ];
311
+ case 2:
312
+ expect.apply(void 0, [
313
+ _state.sent()
314
+ ]).toBe(true);
315
+ return [
316
+ 4,
317
+ px([
318
+ 'project',
319
+ 'add',
320
+ 'My App',
321
+ '--url',
322
+ 'https://other.positronic.sh'
323
+ ], {
324
+ configDir: configDir
325
+ })
326
+ ];
327
+ case 3:
328
+ addDuplicateProject = _state.sent();
329
+ return [
330
+ 4,
331
+ addDuplicateProject.waitForOutput(/already exists/i)
332
+ ];
333
+ case 4:
334
+ expect.apply(void 0, [
335
+ _state.sent()
336
+ ]).toBe(true);
337
+ return [
338
+ 2
339
+ ];
340
+ }
341
+ });
342
+ })();
343
+ });
344
+ it('should reject invalid URLs', function() {
345
+ return _async_to_generator(function() {
346
+ var waitForOutput;
347
+ return _ts_generator(this, function(_state) {
348
+ switch(_state.label){
349
+ case 0:
350
+ return [
351
+ 4,
352
+ px([
353
+ 'project',
354
+ 'add',
355
+ 'My App',
356
+ '--url',
357
+ 'not-a-valid-url'
358
+ ], {
359
+ configDir: configDir
360
+ })
361
+ ];
362
+ case 1:
363
+ waitForOutput = _state.sent().waitForOutput;
364
+ return [
365
+ 4,
366
+ waitForOutput(/invalid/i)
367
+ ];
368
+ case 2:
369
+ expect.apply(void 0, [
370
+ _state.sent()
371
+ ]).toBe(true);
372
+ return [
373
+ 2
374
+ ];
375
+ }
376
+ });
377
+ })();
378
+ });
379
+ it('should handle project names with spaces', function() {
380
+ return _async_to_generator(function() {
381
+ var _ref, waitForOutput, instance, config;
382
+ return _ts_generator(this, function(_state) {
383
+ switch(_state.label){
384
+ case 0:
385
+ return [
386
+ 4,
387
+ px([
388
+ 'project',
389
+ 'add',
390
+ 'My Production App',
391
+ '--url',
392
+ 'https://prod.positronic.sh'
393
+ ], {
394
+ configDir: configDir
395
+ })
396
+ ];
397
+ case 1:
398
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
399
+ return [
400
+ 4,
401
+ waitForOutput(/added/i)
402
+ ];
403
+ case 2:
404
+ expect.apply(void 0, [
405
+ _state.sent()
406
+ ]).toBe(true);
407
+ expect(instance.lastFrame()).toMatch(/my production app/i);
408
+ config = JSON.parse(fs.readFileSync(path.join(configDir, 'config.json'), 'utf-8'));
409
+ expect(config.projects[0].name).toBe('My Production App');
410
+ return [
411
+ 2
412
+ ];
413
+ }
414
+ });
415
+ })();
416
+ });
417
+ });
418
+ describe('project list', function() {
419
+ it('should show empty state when no projects configured', function() {
420
+ return _async_to_generator(function() {
421
+ var instance, output;
422
+ return _ts_generator(this, function(_state) {
423
+ switch(_state.label){
424
+ case 0:
425
+ return [
426
+ 4,
427
+ px([
428
+ 'project',
429
+ 'list'
430
+ ], {
431
+ configDir: configDir
432
+ })
433
+ ];
434
+ case 1:
435
+ instance = _state.sent().instance;
436
+ output = instance.lastFrame() || '';
437
+ expect(output.toLowerCase()).toContain('no projects');
438
+ return [
439
+ 2
440
+ ];
441
+ }
442
+ });
443
+ })();
444
+ });
445
+ it('should list all projects with current indicator', function() {
446
+ return _async_to_generator(function() {
447
+ var instance, output;
448
+ return _ts_generator(this, function(_state) {
449
+ switch(_state.label){
450
+ case 0:
451
+ // Add some projects
452
+ return [
453
+ 4,
454
+ px([
455
+ 'project',
456
+ 'add',
457
+ 'Project One',
458
+ '--url',
459
+ 'https://one.positronic.sh'
460
+ ], {
461
+ configDir: configDir
462
+ })
463
+ ];
464
+ case 1:
465
+ _state.sent();
466
+ return [
467
+ 4,
468
+ px([
469
+ 'project',
470
+ 'add',
471
+ 'Project Two',
472
+ '--url',
473
+ 'https://two.positronic.sh'
474
+ ], {
475
+ configDir: configDir
476
+ })
477
+ ];
478
+ case 2:
479
+ _state.sent();
480
+ return [
481
+ 4,
482
+ px([
483
+ 'project',
484
+ 'select',
485
+ 'Project Two'
486
+ ], {
487
+ configDir: configDir
488
+ })
489
+ ];
490
+ case 3:
491
+ _state.sent();
492
+ return [
493
+ 4,
494
+ px([
495
+ 'project',
496
+ 'list'
497
+ ], {
498
+ configDir: configDir
499
+ })
500
+ ];
501
+ case 4:
502
+ instance = _state.sent().instance;
503
+ output = instance.lastFrame() || '';
504
+ expect(output).toMatch(/project one/i);
505
+ expect(output).toMatch(/project two/i);
506
+ return [
507
+ 2
508
+ ];
509
+ }
510
+ });
511
+ })();
512
+ });
513
+ });
514
+ describe('project select', function() {
515
+ beforeEach(function() {
516
+ return _async_to_generator(function() {
517
+ return _ts_generator(this, function(_state) {
518
+ switch(_state.label){
519
+ case 0:
520
+ // Add some projects for selection tests
521
+ return [
522
+ 4,
523
+ px([
524
+ 'project',
525
+ 'add',
526
+ 'Project Alpha',
527
+ '--url',
528
+ 'https://alpha.positronic.sh'
529
+ ], {
530
+ configDir: configDir
531
+ })
532
+ ];
533
+ case 1:
534
+ _state.sent();
535
+ return [
536
+ 4,
537
+ px([
538
+ 'project',
539
+ 'add',
540
+ 'Project Beta',
541
+ '--url',
542
+ 'https://beta.positronic.sh'
543
+ ], {
544
+ configDir: configDir
545
+ })
546
+ ];
547
+ case 2:
548
+ _state.sent();
549
+ return [
550
+ 4,
551
+ px([
552
+ 'project',
553
+ 'add',
554
+ 'Project Gamma',
555
+ '--url',
556
+ 'https://gamma.positronic.sh'
557
+ ], {
558
+ configDir: configDir
559
+ })
560
+ ];
561
+ case 3:
562
+ _state.sent();
563
+ return [
564
+ 2
565
+ ];
566
+ }
567
+ });
568
+ })();
569
+ });
570
+ it('should select a project by name', function() {
571
+ return _async_to_generator(function() {
572
+ var instance, output, config;
573
+ return _ts_generator(this, function(_state) {
574
+ switch(_state.label){
575
+ case 0:
576
+ return [
577
+ 4,
578
+ px([
579
+ 'project',
580
+ 'select',
581
+ 'Project Beta'
582
+ ], {
583
+ configDir: configDir
584
+ })
585
+ ];
586
+ case 1:
587
+ instance = _state.sent().instance;
588
+ output = instance.lastFrame() || '';
589
+ expect(output.toLowerCase()).toMatch(/switched|selected/);
590
+ expect(output).toMatch(/project beta/i);
591
+ // Verify config was updated
592
+ config = JSON.parse(fs.readFileSync(path.join(configDir, 'config.json'), 'utf-8'));
593
+ expect(config.currentProject).toBe('Project Beta');
594
+ return [
595
+ 2
596
+ ];
597
+ }
598
+ });
599
+ })();
600
+ });
601
+ it('should show error for non-existent project', function() {
602
+ return _async_to_generator(function() {
603
+ var instance, output;
604
+ return _ts_generator(this, function(_state) {
605
+ switch(_state.label){
606
+ case 0:
607
+ return [
608
+ 4,
609
+ px([
610
+ 'project',
611
+ 'select',
612
+ 'Non Existent'
613
+ ], {
614
+ configDir: configDir
615
+ })
616
+ ];
617
+ case 1:
618
+ instance = _state.sent().instance;
619
+ output = instance.lastFrame() || '';
620
+ expect(output.toLowerCase()).toContain('not found');
621
+ expect(output).toMatch(/project alpha/i);
622
+ return [
623
+ 2
624
+ ];
625
+ }
626
+ });
627
+ })();
628
+ });
629
+ it('should show interactive selection when no name provided', function() {
630
+ return _async_to_generator(function() {
631
+ var instance, output;
632
+ return _ts_generator(this, function(_state) {
633
+ switch(_state.label){
634
+ case 0:
635
+ return [
636
+ 4,
637
+ px([
638
+ 'project',
639
+ 'select'
640
+ ], {
641
+ configDir: configDir
642
+ })
643
+ ];
644
+ case 1:
645
+ instance = _state.sent().instance;
646
+ output = instance.lastFrame() || '';
647
+ // In test environment, raw mode isn't supported so it shows a non-interactive list
648
+ expect(output).toMatch(/project alpha/i);
649
+ expect(output).toMatch(/project beta/i);
650
+ expect(output).toMatch(/project gamma/i);
651
+ return [
652
+ 2
653
+ ];
654
+ }
655
+ });
656
+ })();
657
+ });
658
+ });
659
+ describe('project show', function() {
660
+ it('should show no project selected when empty', function() {
661
+ return _async_to_generator(function() {
662
+ var instance, output;
663
+ return _ts_generator(this, function(_state) {
664
+ switch(_state.label){
665
+ case 0:
666
+ return [
667
+ 4,
668
+ px([
669
+ 'project',
670
+ 'show'
671
+ ], {
672
+ configDir: configDir
673
+ })
674
+ ];
675
+ case 1:
676
+ instance = _state.sent().instance;
677
+ output = instance.lastFrame() || '';
678
+ expect(output.toLowerCase()).toContain('no project');
679
+ return [
680
+ 2
681
+ ];
682
+ }
683
+ });
684
+ })();
685
+ });
686
+ it('should show current project details', function() {
687
+ return _async_to_generator(function() {
688
+ var instance, output;
689
+ return _ts_generator(this, function(_state) {
690
+ switch(_state.label){
691
+ case 0:
692
+ return [
693
+ 4,
694
+ px([
695
+ 'project',
696
+ 'add',
697
+ 'My Current Project',
698
+ '--url',
699
+ 'https://current.positronic.sh'
700
+ ], {
701
+ configDir: configDir
702
+ })
703
+ ];
704
+ case 1:
705
+ _state.sent();
706
+ return [
707
+ 4,
708
+ px([
709
+ 'project',
710
+ 'show'
711
+ ], {
712
+ configDir: configDir
713
+ })
714
+ ];
715
+ case 2:
716
+ instance = _state.sent().instance;
717
+ output = instance.lastFrame() || '';
718
+ expect(output).toMatch(/my current project/i);
719
+ expect(output).toContain('https://current.positronic.sh');
720
+ return [
721
+ 2
722
+ ];
723
+ }
724
+ });
725
+ })();
726
+ });
727
+ it('should show other projects count when multiple exist', function() {
728
+ return _async_to_generator(function() {
729
+ var instance, output;
730
+ return _ts_generator(this, function(_state) {
731
+ switch(_state.label){
732
+ case 0:
733
+ return [
734
+ 4,
735
+ px([
736
+ 'project',
737
+ 'add',
738
+ 'Project 1',
739
+ '--url',
740
+ 'https://one.positronic.sh'
741
+ ], {
742
+ configDir: configDir
743
+ })
744
+ ];
745
+ case 1:
746
+ _state.sent();
747
+ return [
748
+ 4,
749
+ px([
750
+ 'project',
751
+ 'add',
752
+ 'Project 2',
753
+ '--url',
754
+ 'https://two.positronic.sh'
755
+ ], {
756
+ configDir: configDir
757
+ })
758
+ ];
759
+ case 2:
760
+ _state.sent();
761
+ return [
762
+ 4,
763
+ px([
764
+ 'project',
765
+ 'add',
766
+ 'Project 3',
767
+ '--url',
768
+ 'https://three.positronic.sh'
769
+ ], {
770
+ configDir: configDir
771
+ })
772
+ ];
773
+ case 3:
774
+ _state.sent();
775
+ return [
776
+ 4,
777
+ px([
778
+ 'project',
779
+ 'show'
780
+ ], {
781
+ configDir: configDir
782
+ })
783
+ ];
784
+ case 4:
785
+ instance = _state.sent().instance;
786
+ output = instance.lastFrame() || '';
787
+ expect(output.toLowerCase()).toContain('2 other');
788
+ return [
789
+ 2
790
+ ];
791
+ }
792
+ });
793
+ })();
794
+ });
795
+ });
796
+ describe('project rm', function() {
797
+ beforeEach(function() {
798
+ return _async_to_generator(function() {
799
+ return _ts_generator(this, function(_state) {
800
+ switch(_state.label){
801
+ case 0:
802
+ // Add some projects for removal tests
803
+ return [
804
+ 4,
805
+ px([
806
+ 'project',
807
+ 'add',
808
+ 'Project A',
809
+ '--url',
810
+ 'https://a.positronic.sh'
811
+ ], {
812
+ configDir: configDir
813
+ })
814
+ ];
815
+ case 1:
816
+ _state.sent();
817
+ return [
818
+ 4,
819
+ px([
820
+ 'project',
821
+ 'add',
822
+ 'Project B',
823
+ '--url',
824
+ 'https://b.positronic.sh'
825
+ ], {
826
+ configDir: configDir
827
+ })
828
+ ];
829
+ case 2:
830
+ _state.sent();
831
+ return [
832
+ 4,
833
+ px([
834
+ 'project',
835
+ 'add',
836
+ 'Project C',
837
+ '--url',
838
+ 'https://c.positronic.sh'
839
+ ], {
840
+ configDir: configDir
841
+ })
842
+ ];
843
+ case 3:
844
+ _state.sent();
845
+ return [
846
+ 2
847
+ ];
848
+ }
849
+ });
850
+ })();
851
+ });
852
+ it('should remove a project successfully', function() {
853
+ return _async_to_generator(function() {
854
+ var _ref, waitForOutput, instance, isReady, output, config;
855
+ return _ts_generator(this, function(_state) {
856
+ switch(_state.label){
857
+ case 0:
858
+ return [
859
+ 4,
860
+ px([
861
+ 'project',
862
+ 'rm',
863
+ 'Project B'
864
+ ], {
865
+ configDir: configDir
866
+ })
867
+ ];
868
+ case 1:
869
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
870
+ return [
871
+ 4,
872
+ waitForOutput(/removed successfully/i)
873
+ ];
874
+ case 2:
875
+ isReady = _state.sent();
876
+ expect(isReady).toBe(true);
877
+ output = instance.lastFrame() || '';
878
+ expect(output).toMatch(/project b/i);
879
+ // Verify project was removed from config
880
+ config = JSON.parse(fs.readFileSync(path.join(configDir, 'config.json'), 'utf-8'));
881
+ expect(config.projects).toHaveLength(2);
882
+ expect(config.projects.find(function(p) {
883
+ return p.name === 'Project B';
884
+ })).toBeUndefined();
885
+ return [
886
+ 2
887
+ ];
888
+ }
889
+ });
890
+ })();
891
+ });
892
+ it('should handle removing the current project', function() {
893
+ return _async_to_generator(function() {
894
+ var _ref, waitForOutput, instance, isReady, output, config;
895
+ return _ts_generator(this, function(_state) {
896
+ switch(_state.label){
897
+ case 0:
898
+ // Select Project B as current
899
+ return [
900
+ 4,
901
+ px([
902
+ 'project',
903
+ 'select',
904
+ 'Project B'
905
+ ], {
906
+ configDir: configDir
907
+ })
908
+ ];
909
+ case 1:
910
+ _state.sent();
911
+ return [
912
+ 4,
913
+ px([
914
+ 'project',
915
+ 'rm',
916
+ 'Project B'
917
+ ], {
918
+ configDir: configDir
919
+ })
920
+ ];
921
+ case 2:
922
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
923
+ return [
924
+ 4,
925
+ waitForOutput(/removed successfully/i)
926
+ ];
927
+ case 3:
928
+ isReady = _state.sent();
929
+ expect(isReady).toBe(true);
930
+ output = instance.lastFrame() || '';
931
+ expect(output).toMatch(/project b/i);
932
+ // Verify current project was switched to another project
933
+ config = JSON.parse(fs.readFileSync(path.join(configDir, 'config.json'), 'utf-8'));
934
+ expect(config.currentProject).not.toBe('Project B');
935
+ expect(config.currentProject).toBeTruthy(); // Should be either Project A or Project C
936
+ return [
937
+ 2
938
+ ];
939
+ }
940
+ });
941
+ })();
942
+ });
943
+ it('should handle removing the last project', function() {
944
+ return _async_to_generator(function() {
945
+ var _ref, waitForOutput, instance, isReady, output, config;
946
+ return _ts_generator(this, function(_state) {
947
+ switch(_state.label){
948
+ case 0:
949
+ // Remove all but one project
950
+ return [
951
+ 4,
952
+ px([
953
+ 'project',
954
+ 'rm',
955
+ 'Project A'
956
+ ], {
957
+ configDir: configDir
958
+ })
959
+ ];
960
+ case 1:
961
+ _state.sent();
962
+ return [
963
+ 4,
964
+ px([
965
+ 'project',
966
+ 'rm',
967
+ 'Project B'
968
+ ], {
969
+ configDir: configDir
970
+ })
971
+ ];
972
+ case 2:
973
+ _state.sent();
974
+ return [
975
+ 4,
976
+ px([
977
+ 'project',
978
+ 'rm',
979
+ 'Project C'
980
+ ], {
981
+ configDir: configDir
982
+ })
983
+ ];
984
+ case 3:
985
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
986
+ return [
987
+ 4,
988
+ waitForOutput(/removed successfully/i)
989
+ ];
990
+ case 4:
991
+ isReady = _state.sent();
992
+ expect(isReady).toBe(true);
993
+ output = instance.lastFrame() || '';
994
+ expect(output.toLowerCase()).toMatch(/no active project/);
995
+ // Verify no projects remain and current project is null
996
+ config = JSON.parse(fs.readFileSync(path.join(configDir, 'config.json'), 'utf-8'));
997
+ expect(config.projects).toHaveLength(0);
998
+ expect(config.currentProject).toBeNull();
999
+ return [
1000
+ 2
1001
+ ];
1002
+ }
1003
+ });
1004
+ })();
1005
+ });
1006
+ it('should show error for non-existent project', function() {
1007
+ return _async_to_generator(function() {
1008
+ var _ref, waitForOutput, instance, isReady, output;
1009
+ return _ts_generator(this, function(_state) {
1010
+ switch(_state.label){
1011
+ case 0:
1012
+ return [
1013
+ 4,
1014
+ px([
1015
+ 'project',
1016
+ 'rm',
1017
+ 'Non Existent'
1018
+ ], {
1019
+ configDir: configDir
1020
+ })
1021
+ ];
1022
+ case 1:
1023
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
1024
+ return [
1025
+ 4,
1026
+ waitForOutput(/failed to remove/i)
1027
+ ];
1028
+ case 2:
1029
+ isReady = _state.sent();
1030
+ expect(isReady).toBe(true);
1031
+ output = instance.lastFrame() || '';
1032
+ expect(output.toLowerCase()).toMatch(/not found/);
1033
+ return [
1034
+ 2
1035
+ ];
1036
+ }
1037
+ });
1038
+ })();
1039
+ });
1040
+ });
1041
+ describe('project command interactions', function() {
1042
+ it('should maintain state across commands', function() {
1043
+ return _async_to_generator(function() {
1044
+ var instance, output;
1045
+ return _ts_generator(this, function(_state) {
1046
+ switch(_state.label){
1047
+ case 0:
1048
+ // Add multiple projects
1049
+ return [
1050
+ 4,
1051
+ px([
1052
+ 'project',
1053
+ 'add',
1054
+ 'First',
1055
+ '--url',
1056
+ 'https://first.positronic.sh'
1057
+ ], {
1058
+ configDir: configDir
1059
+ })
1060
+ ];
1061
+ case 1:
1062
+ _state.sent();
1063
+ return [
1064
+ 4,
1065
+ px([
1066
+ 'project',
1067
+ 'add',
1068
+ 'Second',
1069
+ '--url',
1070
+ 'https://second.positronic.sh'
1071
+ ], {
1072
+ configDir: configDir
1073
+ })
1074
+ ];
1075
+ case 2:
1076
+ _state.sent();
1077
+ return [
1078
+ 4,
1079
+ px([
1080
+ 'project',
1081
+ 'show'
1082
+ ], {
1083
+ configDir: configDir
1084
+ })
1085
+ ];
1086
+ case 3:
1087
+ instance = _state.sent().instance;
1088
+ output = instance.lastFrame() || '';
1089
+ expect(output).toMatch(/first/i);
1090
+ // Switch projects
1091
+ return [
1092
+ 4,
1093
+ px([
1094
+ 'project',
1095
+ 'select',
1096
+ 'Second'
1097
+ ], {
1098
+ configDir: configDir
1099
+ })
1100
+ ];
1101
+ case 4:
1102
+ _state.sent();
1103
+ return [
1104
+ 4,
1105
+ px([
1106
+ 'project',
1107
+ 'show'
1108
+ ], {
1109
+ configDir: configDir
1110
+ })
1111
+ ];
1112
+ case 5:
1113
+ // Verify switch worked
1114
+ instance = _state.sent().instance;
1115
+ output = instance.lastFrame() || '';
1116
+ expect(output).toMatch(/second/i);
1117
+ return [
1118
+ 4,
1119
+ px([
1120
+ 'project',
1121
+ 'list'
1122
+ ], {
1123
+ configDir: configDir
1124
+ })
1125
+ ];
1126
+ case 6:
1127
+ // List should show second project
1128
+ instance = _state.sent().instance;
1129
+ output = instance.lastFrame() || '';
1130
+ expect(output).toMatch(/second/i);
1131
+ return [
1132
+ 2
1133
+ ];
1134
+ }
1135
+ });
1136
+ })();
1137
+ });
1138
+ });
1139
+ describe('CLI Integration: project new', function() {
1140
+ var tmpRoot;
1141
+ var originalLocalPath;
1142
+ beforeEach(function() {
1143
+ // Set POSITRONIC_LOCAL_PATH to use local template instead of npm
1144
+ originalLocalPath = process.env.POSITRONIC_LOCAL_PATH;
1145
+ process.env.POSITRONIC_LOCAL_PATH = path.resolve('.');
1146
+ });
1147
+ afterEach(function() {
1148
+ // Restore original environment
1149
+ if (originalLocalPath) {
1150
+ process.env.POSITRONIC_LOCAL_PATH = originalLocalPath;
1151
+ } else {
1152
+ delete process.env.POSITRONIC_LOCAL_PATH;
1153
+ }
1154
+ if (tmpRoot && fs.existsSync(tmpRoot)) {
1155
+ fs.rmSync(tmpRoot, {
1156
+ recursive: true,
1157
+ force: true
1158
+ });
1159
+ }
1160
+ });
1161
+ it('should create a new project directory and output success message', function() {
1162
+ return _async_to_generator(function() {
1163
+ var projectDir, _ref, waitForOutput, instance, isReady, output;
1164
+ return _ts_generator(this, function(_state) {
1165
+ switch(_state.label){
1166
+ case 0:
1167
+ tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'positronic-new-test-'));
1168
+ projectDir = path.join(tmpRoot, 'my-new-project');
1169
+ return [
1170
+ 4,
1171
+ px([
1172
+ 'project',
1173
+ 'new',
1174
+ projectDir
1175
+ ])
1176
+ ];
1177
+ case 1:
1178
+ _ref = _state.sent(), waitForOutput = _ref.waitForOutput, instance = _ref.instance;
1179
+ return [
1180
+ 4,
1181
+ waitForOutput(/project created successfully/i, 200)
1182
+ ];
1183
+ case 2:
1184
+ isReady = _state.sent();
1185
+ expect(isReady).toBe(true);
1186
+ // Validate CLI output contains the project name
1187
+ output = instance.lastFrame() || '';
1188
+ expect(output.toLowerCase()).toContain('my-new-project');
1189
+ // Ensure project directory and essential files exist
1190
+ expect(fs.existsSync(projectDir)).toBe(true);
1191
+ expect(fs.existsSync(path.join(projectDir, 'positronic.config.json'))).toBe(true);
1192
+ expect(fs.existsSync(path.join(projectDir, 'package.json'))).toBe(true);
1193
+ return [
1194
+ 2
1195
+ ];
1196
+ }
1197
+ });
1198
+ })();
1199
+ });
1200
+ });
1201
+ });