@renkosky/lark-fe-skills 0.1.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.
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ REPO_ROOT="${1:-}"
5
+ FE_PROJECT_PATHS="${2:-}"
6
+ OUTPUT_DIR="${3:-docs/frontend-tasks}"
7
+ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
8
+ REGISTRY_DIR="$SKILL_DIR/config"
9
+ REGISTRY_FILE="$REGISTRY_DIR/repos.json"
10
+
11
+ require_lark_cli() {
12
+ if ! command -v lark-cli >/dev/null 2>&1; then
13
+ cat >&2 <<'EOF'
14
+ Error: lark-cli is required but was not found in PATH.
15
+
16
+ Please install and configure Lark CLI before using lark-fe-task:
17
+
18
+ npm install -g @larksuite/cli
19
+ lark-cli config init
20
+ lark-cli auth login --recommend
21
+
22
+ Official lark-cli documentation:
23
+ https://github.com/larksuite/cli/tree/main
24
+
25
+ If your organization uses a different installation method, install lark-cli first,
26
+ then rerun this command.
27
+ EOF
28
+ exit 127
29
+ fi
30
+ }
31
+
32
+ ensure_local_config_gitignore() {
33
+ local repo_root="$1"
34
+ local gitignore_file="$repo_root/.gitignore"
35
+ local ignore_path=".lark-fe-task/"
36
+
37
+ touch "$gitignore_file"
38
+ if ! grep -Fxq "$ignore_path" "$gitignore_file"; then
39
+ {
40
+ if [[ -s "$gitignore_file" ]]; then
41
+ printf '\n'
42
+ fi
43
+ printf '# lark-fe-task local config\n'
44
+ printf '%s\n' "$ignore_path"
45
+ } >> "$gitignore_file"
46
+ fi
47
+ }
48
+
49
+ if [[ -z "$REPO_ROOT" || -z "$FE_PROJECT_PATHS" ]]; then
50
+ echo "Usage: config.sh <repo-root> <fe-project-paths-comma-separated> [output-dir]" >&2
51
+ exit 2
52
+ fi
53
+
54
+ require_lark_cli
55
+
56
+ if [[ ! -d "$REPO_ROOT" ]]; then
57
+ echo "Repo root does not exist: $REPO_ROOT" >&2
58
+ exit 1
59
+ fi
60
+
61
+ IFS=',' read -r -a FE_PATH_ARRAY <<< "$FE_PROJECT_PATHS"
62
+ for fe_path in "${FE_PATH_ARRAY[@]}"; do
63
+ if [[ -z "$fe_path" ]]; then
64
+ echo "FE project path list contains an empty entry" >&2
65
+ exit 1
66
+ fi
67
+ if [[ ! -d "$REPO_ROOT/$fe_path" ]]; then
68
+ echo "FE project path does not exist: $REPO_ROOT/$fe_path" >&2
69
+ exit 1
70
+ fi
71
+ done
72
+
73
+ CONFIG_DIR="$REPO_ROOT/.lark-fe-task"
74
+ CONFIG_FILE="$CONFIG_DIR/config.env"
75
+ mkdir -p "$CONFIG_DIR"
76
+ mkdir -p "$REGISTRY_DIR"
77
+
78
+ {
79
+ printf 'REPO_ROOT=%q\n' "$REPO_ROOT"
80
+ printf 'FE_PROJECT_PATHS=%q\n' "$FE_PROJECT_PATHS"
81
+ printf 'OUTPUT_DIR=%q\n' "$OUTPUT_DIR"
82
+ } > "$CONFIG_FILE"
83
+ ensure_local_config_gitignore "$REPO_ROOT"
84
+
85
+ /usr/bin/ruby -rjson -rfileutils -rtime -e '
86
+ registry_file, repo_root, fe_project_paths, output_dir = ARGV
87
+ now = Time.now.utc.iso8601
88
+ data =
89
+ if File.exist?(registry_file) && !File.read(registry_file).strip.empty?
90
+ JSON.parse(File.read(registry_file))
91
+ else
92
+ { "version" => 1, "repos" => [] }
93
+ end
94
+ data["version"] ||= 1
95
+ data["repos"] ||= []
96
+ entry = {
97
+ "repoRoot" => repo_root,
98
+ "feProjectPaths" => fe_project_paths.split(","),
99
+ "outputDir" => output_dir,
100
+ "updatedAt" => now
101
+ }
102
+ index = data["repos"].find_index { |repo| repo["repoRoot"] == repo_root }
103
+ if index
104
+ data["repos"][index] = entry
105
+ else
106
+ data["repos"] << entry
107
+ end
108
+ FileUtils.mkdir_p(File.dirname(registry_file))
109
+ File.write(registry_file, JSON.pretty_generate(data) + "\n")
110
+ ' "$REGISTRY_FILE" "$REPO_ROOT" "$FE_PROJECT_PATHS" "$OUTPUT_DIR"
111
+
112
+ cat <<EOF
113
+ Configured lark-fe-task.
114
+ CONFIG_FILE=$CONFIG_FILE
115
+ REGISTRY_FILE=$REGISTRY_FILE
116
+ REPO_ROOT=$REPO_ROOT
117
+ FE_PROJECT_PATHS=$FE_PROJECT_PATHS
118
+ OUTPUT_DIR=$OUTPUT_DIR
119
+ EOF
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ REGISTRY_FILE="$SKILL_DIR/config/repos.json"
6
+
7
+ if [[ ! -f "$REGISTRY_FILE" || ! -s "$REGISTRY_FILE" ]]; then
8
+ cat <<'EOF'
9
+ No lark-fe-task repositories are configured yet.
10
+
11
+ Use:
12
+ +config <repo-root> <fe-project-paths> [output-dir]
13
+ EOF
14
+ exit 0
15
+ fi
16
+
17
+ /usr/bin/ruby -rjson -e '
18
+ registry_file = ARGV[0]
19
+ data = JSON.parse(File.read(registry_file))
20
+ repos = data["repos"] || []
21
+
22
+ if repos.empty?
23
+ puts "No lark-fe-task repositories are configured yet."
24
+ puts
25
+ puts "Use:"
26
+ puts " +config <repo-root> <fe-project-paths> [output-dir]"
27
+ exit 0
28
+ end
29
+
30
+ puts "Configured lark-fe-task repositories:"
31
+ repos.each_with_index do |repo, index|
32
+ fe_paths = Array(repo["feProjectPaths"]).join(",")
33
+ puts
34
+ puts "#{index + 1}. #{repo["repoRoot"]}"
35
+ puts " FE_PROJECT_PATHS=#{fe_paths}"
36
+ puts " OUTPUT_DIR=#{repo["outputDir"]}"
37
+ puts " UPDATED_AT=#{repo["updatedAt"]}"
38
+ end
39
+ ' "$REGISTRY_FILE"
@@ -0,0 +1,212 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ DOC_INPUT="${1:-}"
5
+ REPO_ROOT_ARG="${2:-}"
6
+ REPO_ROOT="$(pwd)"
7
+ FE_PROJECT_PATHS="<not-configured>"
8
+ OUTPUT_DIR="docs/frontend-tasks"
9
+ CONFIG_STATUS="missing"
10
+ CONFIG_SOURCE="<none>"
11
+ SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
12
+ REGISTRY_FILE="$SKILL_DIR/config/repos.json"
13
+
14
+ require_lark_cli() {
15
+ if ! command -v lark-cli >/dev/null 2>&1; then
16
+ cat >&2 <<'EOF'
17
+ Error: lark-cli is required but was not found in PATH.
18
+
19
+ Please install and configure Lark CLI before using lark-fe-task:
20
+
21
+ npm install -g @larksuite/cli
22
+ lark-cli config init
23
+ lark-cli auth login --recommend
24
+
25
+ Official lark-cli documentation:
26
+ https://github.com/larksuite/cli/tree/main
27
+
28
+ If your organization uses a different installation method, install lark-cli first,
29
+ then rerun this command.
30
+ EOF
31
+ exit 127
32
+ fi
33
+ }
34
+
35
+ if [[ -z "$DOC_INPUT" ]]; then
36
+ echo "Usage: plan-lark-doc.sh <lark-doc-title-or-url-or-token> [repo-root]" >&2
37
+ exit 2
38
+ fi
39
+
40
+ require_lark_cli
41
+
42
+ find_config_upwards() {
43
+ local dir="$1"
44
+ while [[ "$dir" != "/" ]]; do
45
+ if [[ -f "$dir/.lark-fe-task/config.env" ]]; then
46
+ printf '%s\n' "$dir/.lark-fe-task/config.env"
47
+ return 0
48
+ fi
49
+ dir="$(dirname "$dir")"
50
+ done
51
+ return 1
52
+ }
53
+
54
+ load_registry_config() {
55
+ if [[ ! -f "$REGISTRY_FILE" || ! -s "$REGISTRY_FILE" ]]; then
56
+ return 1
57
+ fi
58
+
59
+ local registry_output
60
+ local registry_status
61
+ set +e
62
+ registry_output="$(/usr/bin/ruby -rjson -rshellwords -e '
63
+ data = JSON.parse(File.read(ARGV[0]))
64
+ repos = data["repos"] || []
65
+ if repos.empty?
66
+ exit 1
67
+ elsif repos.length == 1
68
+ repo = repos.first
69
+ puts "REPO_ROOT=#{Shellwords.escape(repo["repoRoot"])}"
70
+ puts "FE_PROJECT_PATHS=#{Shellwords.escape(Array(repo["feProjectPaths"]).join(","))}"
71
+ puts "OUTPUT_DIR=#{Shellwords.escape(repo["outputDir"] || "docs/frontend-tasks")}"
72
+ puts "CONFIG_STATUS=registry"
73
+ puts "CONFIG_SOURCE=#{Shellwords.escape(ARGV[0])}"
74
+ else
75
+ warn "Multiple lark-fe-task repositories are configured. Choose one and rerun +plan with its repo root:"
76
+ repos.each_with_index do |repo, index|
77
+ warn "#{index + 1}. #{repo["repoRoot"]} | FE_PROJECT_PATHS=#{Array(repo["feProjectPaths"]).join(",")} | OUTPUT_DIR=#{repo["outputDir"]}"
78
+ end
79
+ exit 3
80
+ end
81
+ ' "$REGISTRY_FILE")"
82
+ registry_status=$?
83
+ set -e
84
+
85
+ if [[ "$registry_status" -eq 3 ]]; then
86
+ exit 3
87
+ fi
88
+ if [[ "$registry_status" -ne 0 ]]; then
89
+ return 1
90
+ fi
91
+
92
+ eval "$registry_output"
93
+ }
94
+
95
+ if [[ -n "$REPO_ROOT_ARG" ]]; then
96
+ REPO_ROOT="$REPO_ROOT_ARG"
97
+ CONFIG_FILE="$REPO_ROOT/.lark-fe-task/config.env"
98
+ if [[ -f "$CONFIG_FILE" ]]; then
99
+ # shellcheck disable=SC1090
100
+ source "$CONFIG_FILE"
101
+ CONFIG_STATUS="loaded"
102
+ CONFIG_SOURCE="$CONFIG_FILE"
103
+ fi
104
+ else
105
+ CONFIG_FILE="$(find_config_upwards "$REPO_ROOT" || true)"
106
+ if [[ -n "$CONFIG_FILE" ]]; then
107
+ # shellcheck disable=SC1090
108
+ source "$CONFIG_FILE"
109
+ CONFIG_STATUS="loaded"
110
+ CONFIG_SOURCE="$CONFIG_FILE"
111
+ else
112
+ load_registry_config || true
113
+ fi
114
+ fi
115
+
116
+ is_url_or_token() {
117
+ [[ "$DOC_INPUT" =~ ^https?:// ]] || [[ "$DOC_INPUT" =~ ^[A-Za-z0-9]{20,}$ ]]
118
+ }
119
+
120
+ slugify() {
121
+ local raw="$1"
122
+ local slug
123
+ slug="$(printf '%s' "$raw" | /usr/bin/ruby -EUTF-8 -e '
124
+ raw = STDIN.read
125
+ issue = raw.match(/(?:PR|pr)[-_[:space:]]*([0-9]{3,})/)
126
+ if issue
127
+ puts "pr-#{issue[1]}"
128
+ exit
129
+ end
130
+
131
+ slug = raw.downcase
132
+ .gsub(/\[[^\]]*\]/, "")
133
+ .gsub(/[^a-z0-9]+/, "-")
134
+ .gsub(/^-+|-+$/, "")
135
+ .gsub(/-+/, "-")
136
+ puts slug
137
+ ')"
138
+ if [[ -z "$slug" ]]; then
139
+ slug="lark-fe-task"
140
+ fi
141
+ printf '%s' "$slug"
142
+ }
143
+
144
+ ensure_output_gitignore() {
145
+ local repo_root="$1"
146
+ local output_dir="$2"
147
+ local gitignore_file="$repo_root/.gitignore"
148
+ local ignore_path="$output_dir"
149
+
150
+ if [[ "$ignore_path" = "$repo_root/"* ]]; then
151
+ ignore_path="${ignore_path#"$repo_root/"}"
152
+ fi
153
+
154
+ ignore_path="${ignore_path#/}"
155
+ ignore_path="${ignore_path%/}/"
156
+
157
+ if [[ -z "$ignore_path" || "$ignore_path" == "../"* || "$ignore_path" == /* ]]; then
158
+ return 0
159
+ fi
160
+
161
+ touch "$gitignore_file"
162
+ if ! grep -Fxq "$ignore_path" "$gitignore_file"; then
163
+ {
164
+ if [[ -s "$gitignore_file" ]]; then
165
+ printf '\n'
166
+ fi
167
+ printf '# lark-fe-task generated task breakdowns\n'
168
+ printf '%s\n' "$ignore_path"
169
+ } >> "$gitignore_file"
170
+ fi
171
+ }
172
+
173
+ DOC_REF="$DOC_INPUT"
174
+ TITLE="$DOC_INPUT"
175
+
176
+ if ! is_url_or_token; then
177
+ SEARCH_JSON="$(lark-cli drive +search --as user --query "$DOC_INPUT" --doc-types docx,doc,wiki --page-size 20 --format json)"
178
+ DOC_REF="$(printf '%s' "$SEARCH_JSON" | /usr/bin/ruby -rjson -e '
179
+ data = JSON.parse(STDIN.read).dig("data", "results") || []
180
+ exact = data.find { |r| (r.dig("title_highlighted") || "").gsub(/<\/?h>/, "").gsub(/<\/?hb>/, "") == ARGV[0] }
181
+ chosen = exact || data.first
182
+ abort("No Lark document found") unless chosen
183
+ puts chosen.dig("result_meta", "url") || chosen.dig("result_meta", "token")
184
+ ' "$DOC_INPUT")"
185
+ fi
186
+
187
+ FETCH_JSON="$(lark-cli docs +fetch --as user --api-version v2 --doc "$DOC_REF" --doc-format markdown)"
188
+ CONTENT="$(printf '%s' "$FETCH_JSON" | /usr/bin/ruby -rjson -e 'puts JSON.parse(STDIN.read).dig("data", "document", "content")')"
189
+ DOC_TITLE="$(printf '%s' "$CONTENT" | sed -n 's/^<title>\(.*\)<\/title>$/\1/p; s/^# \(.*\)$/\1/p' | sed -n '1p')"
190
+
191
+ if [[ -n "$DOC_TITLE" ]]; then
192
+ TITLE="$DOC_TITLE"
193
+ fi
194
+
195
+ OUT_DIR="$REPO_ROOT/$OUTPUT_DIR"
196
+ OUT_FILE="$OUT_DIR/$(date +%F)-$(slugify "$TITLE").md"
197
+ ensure_output_gitignore "$REPO_ROOT" "$OUTPUT_DIR"
198
+
199
+ cat <<EOF
200
+ DOCUMENT_REF=$DOC_REF
201
+ REPO_ROOT=$REPO_ROOT
202
+ CONFIG_STATUS=$CONFIG_STATUS
203
+ CONFIG_SOURCE=$CONFIG_SOURCE
204
+ FE_PROJECT_PATHS=$FE_PROJECT_PATHS
205
+ OUTPUT_PATH=$OUT_FILE
206
+
207
+ --- LARK_DOC_MARKDOWN_BEGIN ---
208
+ $CONTENT
209
+ --- LARK_DOC_MARKDOWN_END ---
210
+
211
+ Next: choose the affected FE project path from FE_PROJECT_PATHS, inspect it, use references/task-breakdown-template.md, then write the final task breakdown to OUTPUT_PATH.
212
+ EOF