@yaawn/workflow 0.4.0 → 0.5.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.
- package/README.md +22 -43
- package/agents/personas/yaawn-product-owner.md +3 -3
- package/agents/skills/yaawn-create-plan/SKILL.md +38 -11
- package/agents/skills/yaawn-extract-learnings/SKILL.md +138 -0
- package/agents/skills/yaawn-grill-bug-report/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-other/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-prototype/SKILL.md +38 -0
- package/agents/skills/yaawn-grill-prototype/references/prototype-checklist.md +23 -0
- package/agents/skills/yaawn-grill-refactor/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-spike/SKILL.md +7 -0
- package/agents/skills/yaawn-grill-story/SKILL.md +7 -0
- package/agents/skills/yaawn-implement-plan/SKILL.md +10 -8
- package/agents/skills/yaawn-implement-plan/scripts/agent.ts +1 -1
- package/agents/skills/yaawn-implement-plan/scripts/overnight.ts +7 -4
- package/agents/skills/yaawn-implement-plan/scripts/parallel.ts +155 -12
- package/agents/skills/yaawn-implement-plan/scripts/processes.ts +161 -1
- package/agents/skills/yaawn-implement-plan/scripts/targets.ts +13 -2
- package/agents/skills/yaawn-implement-plan/scripts/worktrees.ts +219 -52
- package/agents/skills/yaawn-prepare-plan/SKILL.md +20 -19
- package/agents/skills/yaawn-verify-executed-plan/SKILL.md +28 -13
- package/agents/skills/yaawn-verify-executed-plan/scripts/overnight.ts +44 -12
- package/agents/skills/yaawn-write-bug-report/SKILL.md +6 -5
- package/agents/skills/yaawn-write-other/SKILL.md +6 -5
- package/agents/skills/yaawn-write-prototype/SKILL.md +96 -0
- package/agents/skills/yaawn-write-refactor/SKILL.md +6 -5
- package/agents/skills/yaawn-write-spike/SKILL.md +1 -1
- package/agents/skills/yaawn-write-user-story/SKILL.md +3 -2
- package/agents/yaawn-glossary.md +4 -3
- package/config/install-tasks.json +0 -1
- package/config/wizard.json +3 -8
- package/dist/cli/args.js +66 -0
- package/dist/cli/errors.js +103 -0
- package/dist/cli/exit-codes.js +3 -0
- package/dist/cli/index.js +11 -0
- package/dist/cli/install-report.js +92 -0
- package/dist/cli/install.js +124 -0
- package/dist/cli/main.js +17 -0
- package/dist/cli/package-root.js +17 -0
- package/dist/cli/project-root.js +11 -0
- package/dist/cli/run.js +141 -0
- package/dist/cli/session.js +1 -0
- package/dist/cli/terminal.js +5 -0
- package/dist/cli/uninstall.js +89 -0
- package/dist/cli/version.js +7 -0
- package/dist/config/agents.js +108 -0
- package/dist/config/config.js +118 -0
- package/dist/config/errors.js +45 -0
- package/dist/config/index.js +11 -0
- package/dist/config/manifest.js +28 -0
- package/dist/config/messages.js +86 -0
- package/dist/config/names.js +40 -0
- package/dist/config/paths.js +73 -0
- package/dist/config/settings.js +101 -0
- package/dist/config/strict-json.js +119 -0
- package/dist/config/tasks.js +77 -0
- package/dist/config/wizard.js +139 -0
- package/dist/display/index.js +3 -0
- package/dist/display/palette.js +61 -0
- package/dist/display/renderer.js +169 -0
- package/dist/display/report.js +15 -0
- package/dist/display/sanitize.js +10 -0
- package/dist/link/errors.js +34 -0
- package/dist/link/fs-utils.js +79 -0
- package/dist/link/index.js +4 -0
- package/dist/link/install.js +20 -0
- package/dist/link/linker.js +137 -0
- package/dist/link/memory.js +221 -0
- package/dist/link/names.js +30 -0
- package/dist/link/types.js +30 -0
- package/dist/link/uninstall.js +90 -0
- package/dist/sync/apply.js +74 -0
- package/dist/sync/changes.js +31 -0
- package/dist/sync/fs-utils.js +63 -0
- package/dist/sync/index.js +7 -0
- package/dist/sync/install.js +57 -0
- package/dist/sync/languages.js +80 -0
- package/dist/sync/plan.js +146 -0
- package/dist/sync/projects.js +294 -0
- package/dist/sync/uninstall.js +234 -0
- package/dist/wizard/build.js +89 -0
- package/dist/wizard/index.js +5 -0
- package/dist/wizard/items.js +83 -0
- package/dist/wizard/prompts.js +167 -0
- package/dist/wizard/section.js +143 -0
- package/dist/wizard/wizard.js +73 -0
- package/github/ISSUE_TEMPLATE/yaawn-prototype.md +48 -0
- package/package.json +11 -10
- package/agents/instructions/output-styles/yaawn-eli5.instructions.md +0 -45
- package/scripts/install.js +0 -73
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { KIND_MULTISELECT, KIND_PROJECTS_LIST, KIND_SINGLE_SELECT, KIND_TEXT_LIST, } from "../config/index.js";
|
|
2
|
+
import { appendItems, ChecklistFormat, normalize } from "./items.js";
|
|
3
|
+
// eslint-disable-next-line no-control-regex -- matches the C0/C1 control characters an item must not contain
|
|
4
|
+
const CONTROL_CHARACTER = /[\u0000-\u001f\u007f-\u009f]/;
|
|
5
|
+
/** Thrown when an item is blank. */
|
|
6
|
+
export class EmptyItemError extends Error {
|
|
7
|
+
constructor() {
|
|
8
|
+
super("empty item");
|
|
9
|
+
this.name = "EmptyItemError";
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/** Thrown when an item is already listed in the file or was already added in this session. */
|
|
13
|
+
export class DuplicateItemError extends Error {
|
|
14
|
+
item;
|
|
15
|
+
constructor(item) {
|
|
16
|
+
super(`duplicate item: ${item}`);
|
|
17
|
+
this.item = item;
|
|
18
|
+
this.name = "DuplicateItemError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/** Thrown when an item contains control characters, which could drive the terminal or break the file. */
|
|
22
|
+
export class ControlCharacterError extends Error {
|
|
23
|
+
constructor() {
|
|
24
|
+
super("item contains control characters");
|
|
25
|
+
this.name = "ControlCharacterError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const NO_FILE = { fileName: "", path: "", content: "", format: new ChecklistFormat() };
|
|
29
|
+
/** One wizard step with its current state. */
|
|
30
|
+
export class Section {
|
|
31
|
+
id;
|
|
32
|
+
title;
|
|
33
|
+
description;
|
|
34
|
+
kind;
|
|
35
|
+
placeholder;
|
|
36
|
+
existing;
|
|
37
|
+
added = [];
|
|
38
|
+
options;
|
|
39
|
+
file;
|
|
40
|
+
constructor(spec, state = {}) {
|
|
41
|
+
this.id = spec.id;
|
|
42
|
+
this.title = spec.title;
|
|
43
|
+
this.description = spec.description;
|
|
44
|
+
this.kind = spec.kind;
|
|
45
|
+
this.placeholder = spec.placeholder;
|
|
46
|
+
this.existing = [...(state.existing ?? [])];
|
|
47
|
+
this.options = (state.options ?? []).map((option) => ({ ...option }));
|
|
48
|
+
this.file = state.file ?? NO_FILE;
|
|
49
|
+
}
|
|
50
|
+
/** The output file a text-list section appends to. */
|
|
51
|
+
get fileName() {
|
|
52
|
+
return this.file.fileName;
|
|
53
|
+
}
|
|
54
|
+
isTextList() {
|
|
55
|
+
return this.kind === KIND_TEXT_LIST;
|
|
56
|
+
}
|
|
57
|
+
isMultiselect() {
|
|
58
|
+
return this.kind === KIND_MULTISELECT;
|
|
59
|
+
}
|
|
60
|
+
isSingleSelect() {
|
|
61
|
+
return this.kind === KIND_SINGLE_SELECT;
|
|
62
|
+
}
|
|
63
|
+
/** Whether the section collects GitHub project names, which stay in memory instead of a file. */
|
|
64
|
+
isProjectsList() {
|
|
65
|
+
return this.kind === KIND_PROJECTS_LIST;
|
|
66
|
+
}
|
|
67
|
+
/** Whether the section is chosen from options rather than typed. */
|
|
68
|
+
hasOptions() {
|
|
69
|
+
return this.isMultiselect() || this.isSingleSelect();
|
|
70
|
+
}
|
|
71
|
+
/** Checks a typed item without changing the section. */
|
|
72
|
+
checkItem(item) {
|
|
73
|
+
const trimmed = item.trim();
|
|
74
|
+
if (trimmed === "") {
|
|
75
|
+
return { status: "empty" };
|
|
76
|
+
}
|
|
77
|
+
if (CONTROL_CHARACTER.test(trimmed)) {
|
|
78
|
+
return { status: "control-character" };
|
|
79
|
+
}
|
|
80
|
+
if (this.isDuplicate(trimmed)) {
|
|
81
|
+
return { status: "duplicate", item: trimmed };
|
|
82
|
+
}
|
|
83
|
+
return { status: "accepted", item: trimmed };
|
|
84
|
+
}
|
|
85
|
+
/** Trims item and appends it; throws when it is empty, a duplicate or contains control characters. */
|
|
86
|
+
add(item) {
|
|
87
|
+
const check = this.checkItem(item);
|
|
88
|
+
switch (check.status) {
|
|
89
|
+
case "empty":
|
|
90
|
+
throw new EmptyItemError();
|
|
91
|
+
case "control-character":
|
|
92
|
+
throw new ControlCharacterError();
|
|
93
|
+
case "duplicate":
|
|
94
|
+
throw new DuplicateItemError(check.item);
|
|
95
|
+
case "accepted":
|
|
96
|
+
this.added.push(check.item);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Whether item matches an existing or added item after normalization. */
|
|
100
|
+
isDuplicate(item) {
|
|
101
|
+
const key = normalize(item);
|
|
102
|
+
const matches = (known) => normalize(known) === key;
|
|
103
|
+
return this.existing.some(matches) || this.added.some(matches);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Selects exactly the options whose value is in values. A single-select
|
|
107
|
+
* section selects the first value that is one of its options and keeps
|
|
108
|
+
* its selection when none is.
|
|
109
|
+
*/
|
|
110
|
+
setSelected(values) {
|
|
111
|
+
if (this.isSingleSelect()) {
|
|
112
|
+
this.selectFirstOption(values);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
for (const option of this.options) {
|
|
116
|
+
option.selected = values.includes(option.value);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
selectFirstOption(values) {
|
|
120
|
+
const value = values.find((candidate) => this.hasOption(candidate));
|
|
121
|
+
if (value === undefined) {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const option of this.options) {
|
|
125
|
+
option.selected = option.value === value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
hasOption(value) {
|
|
129
|
+
return this.options.some((option) => option.value === value);
|
|
130
|
+
}
|
|
131
|
+
/** The first selected option's value, or "". */
|
|
132
|
+
selectedValue() {
|
|
133
|
+
return this.options.find((option) => option.selected)?.value ?? "";
|
|
134
|
+
}
|
|
135
|
+
/** The values of the selected options in order. */
|
|
136
|
+
selectedValues() {
|
|
137
|
+
return this.options.filter((option) => option.selected).map((option) => option.value);
|
|
138
|
+
}
|
|
139
|
+
/** The file content with the added items appended. */
|
|
140
|
+
updatedContent() {
|
|
141
|
+
return appendItems(this.file.content, this.added, this.file.format);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { isAgent } from "../config/index.js";
|
|
2
|
+
import { normalize } from "./items.js";
|
|
3
|
+
/** The ordered wizard sections. */
|
|
4
|
+
export class Wizard {
|
|
5
|
+
sections;
|
|
6
|
+
constructor(sections) {
|
|
7
|
+
this.sections = sections;
|
|
8
|
+
}
|
|
9
|
+
get length() {
|
|
10
|
+
return this.sections.length;
|
|
11
|
+
}
|
|
12
|
+
/** One change per text-list section, including those without added items so callers can report them as unchanged. */
|
|
13
|
+
changes() {
|
|
14
|
+
return this.sections
|
|
15
|
+
.filter((section) => section.isTextList())
|
|
16
|
+
.map((section) => ({
|
|
17
|
+
fileName: section.fileName,
|
|
18
|
+
path: section.file.path,
|
|
19
|
+
content: section.updatedContent(),
|
|
20
|
+
added: section.added.length,
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
23
|
+
/** The single-select agent section, if the wizard has one. */
|
|
24
|
+
agentSection() {
|
|
25
|
+
return this.sections.find((section) => section.isSingleSelect());
|
|
26
|
+
}
|
|
27
|
+
/** Selects agent in the agent section; values that are not one of its options leave the selection unchanged. */
|
|
28
|
+
selectAgent(agent) {
|
|
29
|
+
this.agentSection()?.setSelected([agent]);
|
|
30
|
+
}
|
|
31
|
+
/** The agent chosen in the agent section, or fallback when the wizard has none or nothing valid is selected. */
|
|
32
|
+
selectedAgent(fallback) {
|
|
33
|
+
const agent = this.agentSection()?.selectedValue();
|
|
34
|
+
return isAgent(agent) ? agent : fallback;
|
|
35
|
+
}
|
|
36
|
+
/** The multiselect section, if the wizard has one. */
|
|
37
|
+
multiselect() {
|
|
38
|
+
return this.sections.find((section) => section.isMultiselect());
|
|
39
|
+
}
|
|
40
|
+
/** The selection of the multiselect section, or fallback when the wizard has none. */
|
|
41
|
+
selectedLanguages(fallback) {
|
|
42
|
+
const section = this.multiselect();
|
|
43
|
+
return section === undefined ? [...fallback] : section.selectedValues();
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The GitHub project names entered in the projects-list section in entry
|
|
47
|
+
* order: trimmed, blanks dropped and case-insensitive duplicates removed.
|
|
48
|
+
* Empty without the section.
|
|
49
|
+
*/
|
|
50
|
+
projectNames() {
|
|
51
|
+
const names = [];
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
for (const section of this.sections.filter((candidate) => candidate.isProjectsList())) {
|
|
54
|
+
for (const name of [...section.existing, ...section.added].map((entry) => entry.trim())) {
|
|
55
|
+
const key = normalize(name);
|
|
56
|
+
if (name !== "" && !seen.has(key)) {
|
|
57
|
+
seen.add(key);
|
|
58
|
+
names.push(name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return names;
|
|
63
|
+
}
|
|
64
|
+
/** The plain result the caller passes on to the install. */
|
|
65
|
+
result(fallback) {
|
|
66
|
+
return {
|
|
67
|
+
agent: this.selectedAgent(fallback.agent),
|
|
68
|
+
changes: this.changes(),
|
|
69
|
+
projectNames: this.projectNames(),
|
|
70
|
+
languages: this.selectedLanguages(fallback.languages),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
about: Build a throwaway version of a feature idea to test with customers.
|
|
3
|
+
assignees: ""
|
|
4
|
+
labels: ["Yaawn: Prototype", "Yaawn: To Be Refined"]
|
|
5
|
+
name: Yaawn Prototype
|
|
6
|
+
projects: []
|
|
7
|
+
title: ""
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
<!-- What should the experiment teach us? State the goal and the success criteria. -->
|
|
11
|
+
|
|
12
|
+
## Goal
|
|
13
|
+
|
|
14
|
+
<!-- What will be built to test it? Describe the experiment, not the implementation. The prototype runs on a prototype/ branch that is never merged. -->
|
|
15
|
+
|
|
16
|
+
## What Will Be Built
|
|
17
|
+
|
|
18
|
+
## Results
|
|
19
|
+
|
|
20
|
+
<!-- Filled in by /yaawn-extract-learnings after the prototype was tested with customers: what was observed against the success criteria, and what was learned. -->
|
|
21
|
+
|
|
22
|
+
## Implementation Plan
|
|
23
|
+
|
|
24
|
+
<!-- How? Talk about this during planning part 3 or during work. -->
|
|
25
|
+
|
|
26
|
+
### Human Readable
|
|
27
|
+
|
|
28
|
+
### Open Questions
|
|
29
|
+
|
|
30
|
+
| # | Questions | Owner | Answer |
|
|
31
|
+
| --- | --------- | ----- | ------ |
|
|
32
|
+
| | | | |
|
|
33
|
+
|
|
34
|
+
### Design Decisions
|
|
35
|
+
|
|
36
|
+
<!-- Document decisions here that were made during planning or a grilling session. -->
|
|
37
|
+
|
|
38
|
+
| # | Decision | Choice | Rationale |
|
|
39
|
+
| --- | -------- | ------ | --------- |
|
|
40
|
+
| | | | |
|
|
41
|
+
|
|
42
|
+
### Files to Change
|
|
43
|
+
|
|
44
|
+
<!-- Bullet list of repo-relative paths of every file the implementation will change. -->
|
|
45
|
+
|
|
46
|
+
### LLM Plan
|
|
47
|
+
|
|
48
|
+
<!-- The plan is written here by /yaawn-save-plan. The Definition of Done lives in `.yaawn/yaawn-definition-of-done.md`. -->
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yaawn/workflow",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Installs .agents/ skill files (skills, instructions, output-styles) into your project via `npx @yaawn/workflow`.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
9
|
"url": "git+https://github.com/markusfalk/yaawn.git",
|
|
@@ -12,24 +13,24 @@
|
|
|
12
13
|
"access": "public"
|
|
13
14
|
},
|
|
14
15
|
"bin": {
|
|
15
|
-
"workflow": "
|
|
16
|
+
"workflow": "dist/cli/main.js"
|
|
16
17
|
},
|
|
17
18
|
"scripts": {
|
|
18
|
-
"
|
|
19
|
+
"prepublishOnly": "npm run build:workflow --prefix ../.."
|
|
19
20
|
},
|
|
20
21
|
"files": [
|
|
21
|
-
"
|
|
22
|
+
"dist/",
|
|
23
|
+
"!dist/**/*.test.*",
|
|
22
24
|
"config/",
|
|
23
25
|
"agents/",
|
|
24
26
|
"!agents/**/*.test.ts",
|
|
25
27
|
"yaawn/",
|
|
26
28
|
"github/"
|
|
27
29
|
],
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"@
|
|
33
|
-
"@yaawn/workflow-win32-x64": "0.4.0"
|
|
30
|
+
"engines": {
|
|
31
|
+
"node": ">=23.5.0 || ^22.13.0 || ^20.17.0"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@clack/prompts": "1.8.1"
|
|
34
35
|
}
|
|
35
36
|
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: ELI5
|
|
3
|
-
description: 'Use only when the user explicitly asks for an ELI5, beginner-level, or plainly-worded explanation. Rewrites chat prose to use analogies, everyday vocabulary, and short sentences. Changes wording only — never code, commands, or file paths.'
|
|
4
|
-
keep-coding-instructions: true
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# ELI5
|
|
8
|
-
|
|
9
|
-
Explain things the way you would to someone smart who has never seen this system before.
|
|
10
|
-
|
|
11
|
-
## Scope
|
|
12
|
-
|
|
13
|
-
This style changes **the prose of chat answers only**. It never changes:
|
|
14
|
-
|
|
15
|
-
- code, identifiers, and type names
|
|
16
|
-
- file paths and shell commands
|
|
17
|
-
- API names, endpoints, and configuration keys
|
|
18
|
-
- error messages, stack traces, and log output
|
|
19
|
-
- diffs and code comments
|
|
20
|
-
- commit messages and PR descriptions
|
|
21
|
-
- i18n keys and i18n values
|
|
22
|
-
- anything inside a fenced code block
|
|
23
|
-
|
|
24
|
-
Reproduce all of the above verbatim. Generated code follows the repository's existing conventions and is never reworded to fit this style.
|
|
25
|
-
|
|
26
|
-
Accuracy outranks every rule below:
|
|
27
|
-
|
|
28
|
-
- Never simplify a technical term into an inaccurate one.
|
|
29
|
-
- Never shorten an error message or a security warning.
|
|
30
|
-
- Never drop a caveat to make a sentence shorter.
|
|
31
|
-
|
|
32
|
-
If a rule in this file would make the answer wrong, the rule loses.
|
|
33
|
-
|
|
34
|
-
If another output style is also active, stop and ask which one applies.
|
|
35
|
-
|
|
36
|
-
## Writing rules
|
|
37
|
-
|
|
38
|
-
- Lead with an analogy or a concrete everyday comparison, then connect it back to the real thing.
|
|
39
|
-
- Use everyday vocabulary. Prefer "reads" over "consumes", "goes wrong" over "fails to resolve".
|
|
40
|
-
- Keep sentences short. One idea per sentence.
|
|
41
|
-
- Introduce every technical term the first time it appears: name it, then say what it does in plain words.
|
|
42
|
-
- Prefer concrete examples over abstract description.
|
|
43
|
-
- Say what something is _for_ before saying how it works.
|
|
44
|
-
- Do not use jargon as a shortcut. If a term is unavoidable, explain it.
|
|
45
|
-
- Keep the answer as short as the question allows. Simple is not the same as long.
|
package/scripts/install.js
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
"use strict";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Launcher for @yaawn/workflow (`npx @yaawn/workflow`).
|
|
6
|
-
*
|
|
7
|
-
* The installer itself is a Go binary shipped in a per-platform optional
|
|
8
|
-
* dependency (`<package name>-<platform>`, e.g. `@yaawn/workflow-<platform>`).
|
|
9
|
-
* The package name is read from this package's package.json, so a rename only
|
|
10
|
-
* touches package.json. This shim finds the binary
|
|
11
|
-
* for the current platform and runs it with the package root, so the binary
|
|
12
|
-
* can read the bundled config and payload.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
const path = require("path");
|
|
16
|
-
const { spawnSync } = require("child_process");
|
|
17
|
-
const { name: packageName } = require("../package.json");
|
|
18
|
-
|
|
19
|
-
const BINARY_NAME = "workflow";
|
|
20
|
-
|
|
21
|
-
const PLATFORM_KEYS = {
|
|
22
|
-
"darwin/arm64": "darwin-arm64",
|
|
23
|
-
"darwin/x64": "darwin-x64",
|
|
24
|
-
"linux/x64": "linux-x64",
|
|
25
|
-
"linux/arm64": "linux-arm64",
|
|
26
|
-
"win32/x64": "win32-x64",
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const currentPlatform = `${process.platform}/${process.arch}`;
|
|
30
|
-
|
|
31
|
-
function fail(message) {
|
|
32
|
-
process.stderr.write(`${BINARY_NAME}: ${message}\n`);
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function resolveBinary() {
|
|
37
|
-
const platformKey = PLATFORM_KEYS[currentPlatform];
|
|
38
|
-
if (!platformKey) {
|
|
39
|
-
fail(
|
|
40
|
-
`unsupported platform ${currentPlatform} — supported: ${Object.keys(PLATFORM_KEYS).join(", ")}`,
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
if (typeof packageName !== "string" || packageName === "") {
|
|
44
|
-
fail("package.json has no package name — reinstall the package");
|
|
45
|
-
}
|
|
46
|
-
const platformPackage = `${packageName}-${platformKey}`;
|
|
47
|
-
try {
|
|
48
|
-
const manifest = require.resolve(`${platformPackage}/package.json`);
|
|
49
|
-
const fileName =
|
|
50
|
-
process.platform === "win32" ? `${BINARY_NAME}.exe` : BINARY_NAME;
|
|
51
|
-
return path.join(path.dirname(manifest), "bin", fileName);
|
|
52
|
-
} catch {
|
|
53
|
-
fail(
|
|
54
|
-
`installer binary package ${platformPackage} is not installed — it may be missing from the registry or skipped by --no-optional`,
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const binary = resolveBinary();
|
|
60
|
-
|
|
61
|
-
const packageRoot = path.join(__dirname, "..");
|
|
62
|
-
const result = spawnSync(
|
|
63
|
-
binary,
|
|
64
|
-
["--package-root", packageRoot, ...process.argv.slice(2)],
|
|
65
|
-
{
|
|
66
|
-
stdio: "inherit",
|
|
67
|
-
},
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
if (result.error)
|
|
71
|
-
fail(`could not start the installer (${result.error.message})`);
|
|
72
|
-
if (result.signal) process.kill(process.pid, result.signal);
|
|
73
|
-
process.exit(result.status === null ? 1 : result.status);
|