@mytegroupinc/myte-core 0.0.28 → 0.0.29

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.
@@ -1,226 +1,226 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("node:fs");
5
- const os = require("node:os");
6
- const path = require("node:path");
7
- const { spawnSync } = require("node:child_process");
8
-
9
- const CLI_PATH = path.resolve(__dirname, "..", "cli.js");
10
-
11
- function parseArgs(argv) {
12
- const args = { _: [] };
13
- for (let index = 0; index < argv.length; index += 1) {
14
- const token = argv[index];
15
- if (!token.startsWith("--")) {
16
- args._.push(token);
17
- continue;
18
- }
19
- const key = token.slice(2);
20
- const next = argv[index + 1];
21
- if (!next || next.startsWith("--")) {
22
- args[key] = true;
23
- continue;
24
- }
25
- args[key] = next;
26
- index += 1;
27
- }
28
- return args;
29
- }
30
-
31
- function requireConfirm(args) {
32
- if (!args["confirm-live"]) {
33
- throw new Error("Refusing to run live mission mutations. Re-run with --confirm-live after backend/frontend deploy.");
34
- }
35
- if (!process.env.MYTE_API_KEY && !process.env.MYTE_PROJECT_API_KEY) {
36
- throw new Error("Missing MYTE_API_KEY or MYTE_PROJECT_API_KEY.");
37
- }
38
- }
39
-
40
- function runCli(cliArgs, cwd, envPatch = {}) {
41
- const result = spawnSync(process.execPath, [CLI_PATH, ...cliArgs], {
42
- cwd,
43
- env: { ...process.env, ...envPatch },
44
- encoding: "utf8",
45
- stdio: ["ignore", "pipe", "pipe"],
46
- });
47
- if (result.status !== 0) {
48
- throw new Error([
49
- `Command failed: myte ${cliArgs.join(" ")}`,
50
- result.stderr || result.stdout || "(no output)",
51
- ].join("\n"));
52
- }
53
- const stdout = String(result.stdout || "").trim();
54
- if (!stdout) return {};
55
- try {
56
- return JSON.parse(stdout);
57
- } catch (err) {
58
- throw new Error(`Expected JSON from myte ${cliArgs.join(" ")}:\n${stdout}`);
59
- }
60
- }
61
-
62
- function writeJson(workspace, name, payload) {
63
- const filePath = path.join(workspace, name);
64
- fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
65
- return filePath;
66
- }
67
-
68
- function collectMissionFileText(workspace) {
69
- const missionsDir = path.join(workspace, "MyteCommandCenter", "data", "missions");
70
- if (!fs.existsSync(missionsDir)) return "";
71
- return fs.readdirSync(missionsDir)
72
- .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml") || name.endsWith(".json"))
73
- .map((name) => fs.readFileSync(path.join(missionsDir, name), "utf8"))
74
- .join("\n");
75
- }
76
-
77
- function assertMissionPresence(workspace, missionId, expectedPresent, label) {
78
- const text = collectMissionFileText(workspace);
79
- const present = text.includes(`mission_id: ${missionId}`) || text.includes(`"mission_id": "${missionId}"`);
80
- if (present !== expectedPresent) {
81
- throw new Error(`${label}: expected mission ${missionId} ${expectedPresent ? "in" : "absent from"} bootstrap mission state.`);
82
- }
83
- }
84
-
85
- function pickSuggestionId(createOutput) {
86
- for (const item of createOutput.items || []) {
87
- const suggestionId = item?.suggestion?.suggestion_id || item?.suggestion_id;
88
- if (suggestionId) return String(suggestionId);
89
- }
90
- return "";
91
- }
92
-
93
- function pickAppliedMissionId(reviewOutput) {
94
- for (const item of reviewOutput.items || []) {
95
- const missionId = item?.applied_mission?.mission_id || item?.mission_id || item?.suggestion?.mission_id;
96
- if (missionId) return String(missionId);
97
- }
98
- return "";
99
- }
100
-
101
- function main() {
102
- const args = parseArgs(process.argv.slice(2));
103
- requireConfirm(args);
104
-
105
- const workspace = path.resolve(
106
- args.workspace || fs.mkdtempSync(path.join(os.tmpdir(), "myte-live-mission-harness-")),
107
- );
108
- fs.mkdirSync(workspace, { recursive: true });
109
-
110
- const actorScope = String(args["actor-scope"] || `live-disposable-harness-${Date.now()}`);
111
- const baseArgs = [];
112
- if (args["base-url"]) {
113
- baseArgs.push("--base-url", String(args["base-url"]));
114
- }
115
-
116
- const title = String(args.title || `Disposable mission harness ${new Date().toISOString()}`);
117
- const description = String(args.description || "Disposable mission used to verify Myte mission create, approve, archive, and cleanup flows.");
118
- const reason = String(args.reason || "Disposable live mission harness verification");
119
-
120
- console.log(`Workspace: ${workspace}`);
121
- console.log(`Actor scope: ${actorScope}`);
122
-
123
- runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
124
-
125
- const createFile = writeJson(workspace, "mission-create.json", {
126
- items: [
127
- {
128
- change_type: "create",
129
- change_description: "Create disposable mission for live harness verification",
130
- change_set: {
131
- title,
132
- description,
133
- acceptance_criteria: [
134
- "Mission can be approved from the suggestion review loop.",
135
- "Mission can be archived without hard delete.",
136
- "Archived missions disappear from normal bootstrap state.",
137
- ],
138
- labels: ["harness", "disposable"],
139
- },
140
- },
141
- ],
142
- });
143
- const createOutput = runCli([
144
- "suggestions",
145
- "create",
146
- "--file",
147
- createFile,
148
- "--actor-scope",
149
- actorScope,
150
- "--no-sync",
151
- "--json",
152
- ...baseArgs,
153
- ], workspace);
154
- const suggestionId = pickSuggestionId(createOutput);
155
- if (!suggestionId) {
156
- throw new Error(`Suggestion create did not return a suggestion id: ${JSON.stringify(createOutput, null, 2)}`);
157
- }
158
-
159
- const reviewFile = writeJson(workspace, "mission-review.json", {
160
- items: [
161
- {
162
- suggestion_id: suggestionId,
163
- action: "approve",
164
- review_action: "approve",
165
- final_change_set: {
166
- title,
167
- description,
168
- acceptance_criteria: [
169
- "Mission can be approved from the suggestion review loop.",
170
- "Mission can be archived without hard delete.",
171
- "Archived missions disappear from normal bootstrap state.",
172
- ],
173
- labels: ["harness", "disposable"],
174
- },
175
- },
176
- ],
177
- });
178
- const reviewOutput = runCli([
179
- "suggestions",
180
- "review",
181
- "--file",
182
- reviewFile,
183
- "--actor-scope",
184
- actorScope,
185
- "--no-sync",
186
- "--json",
187
- ...baseArgs,
188
- ], workspace);
189
- const missionId = pickAppliedMissionId(reviewOutput);
190
- if (!missionId) {
191
- throw new Error(`Suggestion approval did not return an applied mission id: ${JSON.stringify(reviewOutput, null, 2)}`);
192
- }
193
-
194
- const archiveOutput = runCli([
195
- "mission",
196
- "archive",
197
- "--mission-ids",
198
- missionId,
199
- "--reason",
200
- reason,
201
- "--json",
202
- ...baseArgs,
203
- ], workspace);
204
- if (!Number(archiveOutput.updated_count || 0) && !Number(archiveOutput.unchanged_count || 0)) {
205
- throw new Error(`Archive did not update or confirm mission state: ${JSON.stringify(archiveOutput, null, 2)}`);
206
- }
207
- runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
208
- assertMissionPresence(workspace, missionId, false, "Archived bootstrap check");
209
-
210
- const finalState = "archived";
211
-
212
- console.log(JSON.stringify({
213
- status: "success",
214
- workspace,
215
- suggestion_id: suggestionId,
216
- mission_id: missionId,
217
- final_state: finalState,
218
- }, null, 2));
219
- }
220
-
221
- try {
222
- main();
223
- } catch (err) {
224
- console.error(err?.message || err);
225
- process.exit(1);
226
- }
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require("node:fs");
5
+ const os = require("node:os");
6
+ const path = require("node:path");
7
+ const { spawnSync } = require("node:child_process");
8
+
9
+ const CLI_PATH = path.resolve(__dirname, "..", "cli.js");
10
+
11
+ function parseArgs(argv) {
12
+ const args = { _: [] };
13
+ for (let index = 0; index < argv.length; index += 1) {
14
+ const token = argv[index];
15
+ if (!token.startsWith("--")) {
16
+ args._.push(token);
17
+ continue;
18
+ }
19
+ const key = token.slice(2);
20
+ const next = argv[index + 1];
21
+ if (!next || next.startsWith("--")) {
22
+ args[key] = true;
23
+ continue;
24
+ }
25
+ args[key] = next;
26
+ index += 1;
27
+ }
28
+ return args;
29
+ }
30
+
31
+ function requireConfirm(args) {
32
+ if (!args["confirm-live"]) {
33
+ throw new Error("Refusing to run live mission mutations. Re-run with --confirm-live after backend/frontend deploy.");
34
+ }
35
+ if (!process.env.MYTE_API_KEY && !process.env.MYTE_PROJECT_API_KEY) {
36
+ throw new Error("Missing MYTE_API_KEY or MYTE_PROJECT_API_KEY.");
37
+ }
38
+ }
39
+
40
+ function runCli(cliArgs, cwd, envPatch = {}) {
41
+ const result = spawnSync(process.execPath, [CLI_PATH, ...cliArgs], {
42
+ cwd,
43
+ env: { ...process.env, ...envPatch },
44
+ encoding: "utf8",
45
+ stdio: ["ignore", "pipe", "pipe"],
46
+ });
47
+ if (result.status !== 0) {
48
+ throw new Error([
49
+ `Command failed: myte ${cliArgs.join(" ")}`,
50
+ result.stderr || result.stdout || "(no output)",
51
+ ].join("\n"));
52
+ }
53
+ const stdout = String(result.stdout || "").trim();
54
+ if (!stdout) return {};
55
+ try {
56
+ return JSON.parse(stdout);
57
+ } catch (err) {
58
+ throw new Error(`Expected JSON from myte ${cliArgs.join(" ")}:\n${stdout}`);
59
+ }
60
+ }
61
+
62
+ function writeJson(workspace, name, payload) {
63
+ const filePath = path.join(workspace, name);
64
+ fs.writeFileSync(filePath, JSON.stringify(payload, null, 2), "utf8");
65
+ return filePath;
66
+ }
67
+
68
+ function collectMissionFileText(workspace) {
69
+ const missionsDir = path.join(workspace, "MyteCommandCenter", "data", "missions");
70
+ if (!fs.existsSync(missionsDir)) return "";
71
+ return fs.readdirSync(missionsDir)
72
+ .filter((name) => name.endsWith(".yml") || name.endsWith(".yaml") || name.endsWith(".json"))
73
+ .map((name) => fs.readFileSync(path.join(missionsDir, name), "utf8"))
74
+ .join("\n");
75
+ }
76
+
77
+ function assertMissionPresence(workspace, missionId, expectedPresent, label) {
78
+ const text = collectMissionFileText(workspace);
79
+ const present = text.includes(`mission_id: ${missionId}`) || text.includes(`"mission_id": "${missionId}"`);
80
+ if (present !== expectedPresent) {
81
+ throw new Error(`${label}: expected mission ${missionId} ${expectedPresent ? "in" : "absent from"} bootstrap mission state.`);
82
+ }
83
+ }
84
+
85
+ function pickSuggestionId(createOutput) {
86
+ for (const item of createOutput.items || []) {
87
+ const suggestionId = item?.suggestion?.suggestion_id || item?.suggestion_id;
88
+ if (suggestionId) return String(suggestionId);
89
+ }
90
+ return "";
91
+ }
92
+
93
+ function pickAppliedMissionId(reviewOutput) {
94
+ for (const item of reviewOutput.items || []) {
95
+ const missionId = item?.applied_mission?.mission_id || item?.mission_id || item?.suggestion?.mission_id;
96
+ if (missionId) return String(missionId);
97
+ }
98
+ return "";
99
+ }
100
+
101
+ function main() {
102
+ const args = parseArgs(process.argv.slice(2));
103
+ requireConfirm(args);
104
+
105
+ const workspace = path.resolve(
106
+ args.workspace || fs.mkdtempSync(path.join(os.tmpdir(), "myte-live-mission-harness-")),
107
+ );
108
+ fs.mkdirSync(workspace, { recursive: true });
109
+
110
+ const actorScope = String(args["actor-scope"] || `live-disposable-harness-${Date.now()}`);
111
+ const baseArgs = [];
112
+ if (args["base-url"]) {
113
+ baseArgs.push("--base-url", String(args["base-url"]));
114
+ }
115
+
116
+ const title = String(args.title || `Disposable mission harness ${new Date().toISOString()}`);
117
+ const description = String(args.description || "Disposable mission used to verify Myte mission create, approve, archive, and cleanup flows.");
118
+ const reason = String(args.reason || "Disposable live mission harness verification");
119
+
120
+ console.log(`Workspace: ${workspace}`);
121
+ console.log(`Actor scope: ${actorScope}`);
122
+
123
+ runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
124
+
125
+ const createFile = writeJson(workspace, "mission-create.json", {
126
+ items: [
127
+ {
128
+ change_type: "create",
129
+ change_description: "Create disposable mission for live harness verification",
130
+ change_set: {
131
+ title,
132
+ description,
133
+ acceptance_criteria: [
134
+ "Mission can be approved from the suggestion review loop.",
135
+ "Mission can be archived without hard delete.",
136
+ "Archived missions disappear from normal bootstrap state.",
137
+ ],
138
+ labels: ["harness", "disposable"],
139
+ },
140
+ },
141
+ ],
142
+ });
143
+ const createOutput = runCli([
144
+ "suggestions",
145
+ "create",
146
+ "--file",
147
+ createFile,
148
+ "--actor-scope",
149
+ actorScope,
150
+ "--no-sync",
151
+ "--json",
152
+ ...baseArgs,
153
+ ], workspace);
154
+ const suggestionId = pickSuggestionId(createOutput);
155
+ if (!suggestionId) {
156
+ throw new Error(`Suggestion create did not return a suggestion id: ${JSON.stringify(createOutput, null, 2)}`);
157
+ }
158
+
159
+ const reviewFile = writeJson(workspace, "mission-review.json", {
160
+ items: [
161
+ {
162
+ suggestion_id: suggestionId,
163
+ action: "approve",
164
+ review_action: "approve",
165
+ final_change_set: {
166
+ title,
167
+ description,
168
+ acceptance_criteria: [
169
+ "Mission can be approved from the suggestion review loop.",
170
+ "Mission can be archived without hard delete.",
171
+ "Archived missions disappear from normal bootstrap state.",
172
+ ],
173
+ labels: ["harness", "disposable"],
174
+ },
175
+ },
176
+ ],
177
+ });
178
+ const reviewOutput = runCli([
179
+ "suggestions",
180
+ "review",
181
+ "--file",
182
+ reviewFile,
183
+ "--actor-scope",
184
+ actorScope,
185
+ "--no-sync",
186
+ "--json",
187
+ ...baseArgs,
188
+ ], workspace);
189
+ const missionId = pickAppliedMissionId(reviewOutput);
190
+ if (!missionId) {
191
+ throw new Error(`Suggestion approval did not return an applied mission id: ${JSON.stringify(reviewOutput, null, 2)}`);
192
+ }
193
+
194
+ const archiveOutput = runCli([
195
+ "mission",
196
+ "archive",
197
+ "--mission-ids",
198
+ missionId,
199
+ "--reason",
200
+ reason,
201
+ "--json",
202
+ ...baseArgs,
203
+ ], workspace);
204
+ if (!Number(archiveOutput.updated_count || 0) && !Number(archiveOutput.unchanged_count || 0)) {
205
+ throw new Error(`Archive did not update or confirm mission state: ${JSON.stringify(archiveOutput, null, 2)}`);
206
+ }
207
+ runCli(["bootstrap", "--json", "--actor-scope", actorScope, ...baseArgs], workspace);
208
+ assertMissionPresence(workspace, missionId, false, "Archived bootstrap check");
209
+
210
+ const finalState = "archived";
211
+
212
+ console.log(JSON.stringify({
213
+ status: "success",
214
+ workspace,
215
+ suggestion_id: suggestionId,
216
+ mission_id: missionId,
217
+ final_state: finalState,
218
+ }, null, 2));
219
+ }
220
+
221
+ try {
222
+ main();
223
+ } catch (err) {
224
+ console.error(err?.message || err);
225
+ process.exit(1);
226
+ }