@ykdz/template 0.0.33 → 0.0.36

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 (98) hide show
  1. package/dist/application.d.ts +2 -1
  2. package/dist/application.js +64 -75
  3. package/dist/main.js +6 -2
  4. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.d.ts +50 -36
  5. package/node_modules/@ykdz/template-builtin-presets/dist/src/foundation.js +970 -365
  6. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.d.ts +24 -0
  7. package/node_modules/@ykdz/template-builtin-presets/dist/src/generation-record.js +154 -0
  8. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.d.ts +2 -2
  9. package/node_modules/@ykdz/template-builtin-presets/dist/src/index.js +1 -1
  10. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.d.ts +31 -2
  11. package/node_modules/@ykdz/template-builtin-presets/dist/src/rust-bin/definition.js +29 -33
  12. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.d.ts +1 -0
  13. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/development-container.js +17 -0
  14. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.d.ts +4 -3
  15. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/typescript.js +9 -5
  16. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.d.ts +1 -1
  17. package/node_modules/@ykdz/template-builtin-presets/dist/src/shared/vue.js +1 -2
  18. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.d.ts +31 -2
  19. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-cli/definition.js +79 -58
  20. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.d.ts +31 -2
  21. package/node_modules/@ykdz/template-builtin-presets/dist/src/ts-lib/definition.js +31 -23
  22. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.d.ts +22 -2
  23. package/node_modules/@ykdz/template-builtin-presets/dist/src/vike-app/definition.js +61 -35
  24. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.d.ts +26 -2
  25. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-app/definition.js +19 -8
  26. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.d.ts +22 -2
  27. package/node_modules/@ykdz/template-builtin-presets/dist/src/vue-hono-app/definition.js +31 -21
  28. package/node_modules/@ykdz/template-builtin-presets/package.json +12 -11
  29. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment-diagnostics.yml +1 -1
  30. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.github/workflows/check-deployment.yml +1 -1
  31. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/.pnpmfile.mjs +19 -0
  32. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/gitignore +1 -0
  33. package/node_modules/@ykdz/template-builtin-presets/templates/foundation/tsconfig.packing-hook.json +8 -0
  34. package/node_modules/@ykdz/template-builtin-presets/templates/shared/devcontainer/github-cli.Dockerfile +9 -0
  35. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/CHANGELOG.md.template +17 -0
  36. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/RELEASING.md +17 -0
  37. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/artifact.ts +1271 -0
  38. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/changelog.ts +283 -0
  39. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-artifact.ts +99 -0
  40. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/check-readiness.ts +66 -0
  41. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/cli-command-identity.ts +4 -0
  42. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/handoff.ts +450 -0
  43. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/publish.ts +483 -0
  44. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/readiness.ts +1125 -0
  45. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.ts +819 -0
  46. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/release.yml +119 -0
  47. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/root-tsconfig.json +13 -0
  48. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/tsconfig.json +4 -0
  49. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication/turbo.json +6 -0
  50. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/README.md +48 -0
  51. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/CHANGELOG.md.template +17 -0
  52. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-APACHE-2.0.txt +202 -0
  53. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/LICENSE-MIT.txt +21 -0
  54. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/assets/README.md.template +5 -0
  55. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/bridge.ts +3336 -0
  56. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/publication-setup/setup.sh +221 -0
  57. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli-command-identity.ts +133 -0
  58. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/cli.ts +4 -3
  59. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/src/main.ts +5 -4
  60. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/integration/command.test.ts +14 -13
  61. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/test/unit/cli-command-identity.test.ts +76 -0
  62. package/node_modules/@ykdz/template-builtin-presets/templates/ts-cli/tsconfig.build.json +2 -0
  63. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/src/readiness.ts +1 -1
  64. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/test/global-setup.ts +21 -0
  65. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/tsconfig.json +6 -1
  66. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db/vitest.config.ts +7 -0
  67. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/drizzle.config.ts +5 -1
  68. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/playwright.config.ts +102 -22
  69. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/test/playwright-teardown.ts +12 -0
  70. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/vite.config.ts +39 -1
  71. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/playwright.config.ts +48 -7
  72. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/playwright.config.ts +59 -8
  73. package/node_modules/@ykdz/template-core/dist/module-graph.d.ts +15 -53
  74. package/node_modules/@ykdz/template-core/dist/module-graph.js +134 -101
  75. package/node_modules/@ykdz/template-core/dist/package-contribution.d.ts +13 -1
  76. package/node_modules/@ykdz/template-core/dist/package-contribution.js +92 -0
  77. package/node_modules/@ykdz/template-core/dist/pnpm-workspace.yaml +14 -10
  78. package/node_modules/@ykdz/template-core/dist/preset-definition.d.ts +71 -13
  79. package/node_modules/@ykdz/template-core/dist/preset-definition.js +16 -1
  80. package/node_modules/@ykdz/template-core/dist/project-blueprint.d.ts +56 -0
  81. package/node_modules/@ykdz/template-core/dist/project-blueprint.js +271 -0
  82. package/node_modules/@ykdz/template-core/dist/project-linking-v2.d.ts +2 -2
  83. package/node_modules/@ykdz/template-core/dist/project-linking-v2.js +2 -2
  84. package/node_modules/@ykdz/template-core/dist/project-projection.d.ts +2 -1
  85. package/node_modules/@ykdz/template-core/dist/project-projection.js +107 -20
  86. package/node_modules/@ykdz/template-core/dist/renderer.d.ts +0 -2
  87. package/node_modules/@ykdz/template-core/dist/renderer.js +17 -38
  88. package/node_modules/@ykdz/template-core/dist/template-boundary-check.d.ts +2 -2
  89. package/node_modules/@ykdz/template-core/dist/template-boundary-check.js +6 -0
  90. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.d.ts +2 -0
  91. package/node_modules/@ykdz/template-core/dist/toolchain-resolution.js +9 -3
  92. package/package.json +3 -3
  93. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/db-migrations/scripts/prepare-database.ts +0 -45
  94. package/node_modules/@ykdz/template-builtin-presets/templates/vike-app/web/scripts/run-playwright.ts +0 -151
  95. package/node_modules/@ykdz/template-builtin-presets/templates/vue-app/scripts/run-playwright.ts +0 -62
  96. package/node_modules/@ykdz/template-builtin-presets/templates/vue-hono-app/web/scripts/run-playwright.ts +0 -103
  97. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.d.ts +0 -33
  98. package/node_modules/@ykdz/template-core/dist/project-blueprint-v2.js +0 -182
@@ -0,0 +1,221 @@
1
+ #!/usr/bin/env bash
2
+ # One-time, local-only setup interface. The generated body is checked Template Source.
3
+
4
+ set -u
5
+
6
+ package_path="{{PUBLIC_CLI_PACKAGE_PATH}}"
7
+ script_dir=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)
8
+ repository_root=$(CDPATH='' cd -- "$script_dir/../.." && pwd)
9
+ mode="run"
10
+ status_requested=false
11
+ json_requested=false
12
+ non_interactive=false
13
+ debug=false
14
+ package_name=""
15
+ command_name=""
16
+ description=""
17
+ license=""
18
+ copyright_holder=""
19
+ repository=""
20
+
21
+ usage() {
22
+ cat <<'USAGE'
23
+ Usage: ./scripts/npm-publication-setup/setup.sh [public-fact options] [--non-interactive] [--debug]
24
+ ./scripts/npm-publication-setup/setup.sh --status --json
25
+ ./scripts/npm-publication-setup/setup.sh --help
26
+ USAGE
27
+ }
28
+
29
+ diagnostic() {
30
+ local kind=$1 code=$2 observed expected next
31
+ observed=$(sanitize "$3")
32
+ expected=$(sanitize "$4")
33
+ next=$(sanitize "$5")
34
+ printf '%s %s\nObserved: %s\nExpected: %s\nNext action: %s\n' "$kind" "$code" "$observed" "$expected" "$next" >&2
35
+ }
36
+
37
+ fail() { diagnostic "$1" "$2" "$3" "$4" "$5"; exit "$6"; }
38
+
39
+ redact() {
40
+ sed -E 's#(https?://)[^/@[:space:]]+@#\1[REDACTED]@#g; s#(token|password|otp|session|authorization|auth|config)[=:][^[:space:]]+#\1=[REDACTED]#gI; s#(bearer|basic)[[:space:]]+[^[:space:]]+#\1 [REDACTED]#gI'
41
+ }
42
+
43
+ sanitize() {
44
+ printf '%s' "$1" | LC_ALL=C tr -d '\000-\037\177-\237' | redact
45
+ }
46
+
47
+ prompt_public_fact() {
48
+ local label=$1 variable=$2
49
+ printf '%s: ' "$label"
50
+ # shellcheck disable=SC2229
51
+ if ! IFS= read -r "$variable"; then
52
+ fail "ACTION REQUIRED" public-fact-required "end of input" "a complete public package fact" "Run with all public flags or enter the remaining facts interactively." 3
53
+ fi
54
+ }
55
+
56
+ require_value() {
57
+ [ "$#" -eq 2 ] || fail ERROR publication-setup-usage "missing flag value" "a value for $1" "Run --help and provide public facts." 2
58
+ }
59
+
60
+ status_json_intent=false
61
+ status_flag_seen=false
62
+ json_flag_seen=false
63
+ for argument in "$@"; do
64
+ [ "$argument" = "--status" ] && status_flag_seen=true
65
+ [ "$argument" = "--json" ] && json_flag_seen=true
66
+ done
67
+ [ "$status_flag_seen" = true ] && [ "$json_flag_seen" = true ] && status_json_intent=true
68
+
69
+ status_usage_json() {
70
+ printf '%s\n' '{"schemaVersion":1,"currentStage":{"id":"check-prerequisites","number":1,"name":"Check prerequisites"},"observations":{"packagePath":null,"packageName":null,"commandName":null,"version":null,"repository":null,"readiness":"unavailable","git":{"workingTree":null,"currentBranch":null,"defaultBranch":null,"headMatchesRemoteDefault":null}},"blockers":[{"code":"publication-setup-usage","observed":"invalid status arguments","expected":"--status --json only","nextAction":"Run ./scripts/npm-publication-setup/setup.sh --status --json."}],"nextAction":{"kind":"run","command":"./scripts/npm-publication-setup/setup.sh"}}'
71
+ exit 2
72
+ }
73
+
74
+ usage_fail() {
75
+ if [ "$status_json_intent" = true ]; then status_usage_json; fi
76
+ fail "$@"
77
+ }
78
+
79
+ while [ "$#" -gt 0 ]; do
80
+ case "$1" in
81
+ --help) [ "$#" -eq 1 ] || usage_fail ERROR publication-setup-usage "--help with other arguments" "--help alone" "Run --help by itself." 2; usage; exit 0 ;;
82
+ --status) mode="status"; status_requested=true ;;
83
+ --json) json_requested=true ;;
84
+ --non-interactive) non_interactive=true ;;
85
+ --debug) debug=true ;;
86
+ --package-name|--bin|--description|--license|--copyright-holder|--repository)
87
+ flag=$1; shift; [ "$#" -gt 0 ] || usage_fail ERROR publication-setup-usage "missing value" "a value for $flag" "Run --help and provide public facts." 2
88
+ case "$flag" in
89
+ --package-name) package_name=$1 ;; --bin) command_name=$1 ;; --description) description=$1 ;;
90
+ --license) license=$1 ;; --copyright-holder) copyright_holder=$1 ;; --repository) repository=$1 ;;
91
+ esac ;;
92
+ --yes|--password|--otp|--token|--secret|--session|--release-date|--*)
93
+ usage_fail ERROR publication-setup-usage "$1" "a supported public option" "Run --help; credentials and automatic acceptance are unsupported." 2 ;;
94
+ *) usage_fail ERROR publication-setup-usage "$1" "no positional arguments" "Run --help." 2 ;;
95
+ esac
96
+ shift
97
+ done
98
+
99
+ if [ "$status_requested" != "$json_requested" ]; then
100
+ usage_fail ERROR publication-setup-usage "--status and --json must be paired" "--status --json" "Run --status --json." 2
101
+ fi
102
+ if [ "$json_requested" = true ] && [ "$mode" != "status" ]; then
103
+ usage_fail ERROR publication-setup-usage "--json without --status" "--status --json" "Run --status --json." 2
104
+ fi
105
+
106
+ if [ "$mode" = "status" ]; then
107
+ if [ "$non_interactive" != false ] || [ "$debug" != false ] || [ -n "$package_name$command_name$description$license$copyright_holder$repository" ]; then
108
+ printf '%s\n' '{"schemaVersion":1,"currentStage":{"id":"check-prerequisites","number":1,"name":"Check prerequisites"},"observations":{"packagePath":null,"packageName":null,"commandName":null,"version":null,"repository":null,"readiness":"unavailable","git":{"workingTree":null,"currentBranch":null,"defaultBranch":null,"headMatchesRemoteDefault":null}},"blockers":[{"code":"publication-setup-usage","observed":"invalid status arguments","expected":"--status --json only","nextAction":"Run ./scripts/npm-publication-setup/setup.sh --status --json."}],"nextAction":{"kind":"run","command":"./scripts/npm-publication-setup/setup.sh"}}'
109
+ exit 2
110
+ fi
111
+ REPOSITORY_ROOT="$repository_root" node --conditions=source "$script_dir/bridge.ts" status
112
+ exit $?
113
+ fi
114
+
115
+ cd "$repository_root" || fail ERROR repository-root "unreadable repository root" "the generated repository root" "Run setup from its generated directory." 5
116
+ printf 'STAGE 1/9 Check prerequisites\nCHECK local-toolchain\n'
117
+ for command in bash node pnpm git gh; do command -v "$command" >/dev/null 2>&1 || fail ERROR prerequisite-command "$command is unavailable" "required local command $command" "Install the generated repository toolchain and retry." 5; done
118
+ REPOSITORY_ROOT="$repository_root" node --conditions=source "$script_dir/bridge.ts" preflight
119
+ preflight_status=$?
120
+ [ "$preflight_status" -eq 0 ] || exit "$preflight_status"
121
+ printf 'OK prerequisites\n'
122
+
123
+ existing_public=false
124
+ if node --input-type=module - "$repository_root/$package_path/package.json" <<'NODE' >/dev/null 2>&1
125
+ import { readFileSync } from "node:fs"; const manifest = JSON.parse(readFileSync(process.argv[2], "utf8")); process.exit(manifest.private || manifest.version !== "1.0.0" ? 1 : 0);
126
+ NODE
127
+ then
128
+ existing_public=true
129
+ fi
130
+ if [ "$non_interactive" = false ] && [ "$existing_public" = false ]; then
131
+ [ -n "$package_name" ] || prompt_public_fact "Package name" package_name
132
+ [ -n "$command_name" ] || prompt_public_fact "Command name" command_name
133
+ [ -n "$description" ] || prompt_public_fact "Public description" description
134
+ [ -n "$license" ] || prompt_public_fact "SPDX license" license
135
+ [ -n "$copyright_holder" ] || prompt_public_fact "Copyright holder" copyright_holder
136
+ [ -n "$repository" ] || prompt_public_fact "Public GitHub repository" repository
137
+ fi
138
+
139
+ printf 'STAGE 2/9 Configure the public package\n'
140
+ PACKAGE_NAME="$package_name" COMMAND_NAME="$command_name" DESCRIPTION="$description" LICENSE_NAME="$license" COPYRIGHT_HOLDER="$copyright_holder" REPOSITORY_URL="$repository" REPOSITORY_ROOT="$repository_root" SETUP_DIR="$script_dir" node --conditions=source "$script_dir/bridge.ts" configure
141
+ configuration_status=$?
142
+ [ "$configuration_status" -eq 0 ] || exit "$configuration_status"
143
+ printf 'OK public-package-configured\nSTAGE 3/9 Commit the publication configuration\n'
144
+ if ! working_tree=$(git status --porcelain 2>/dev/null); then
145
+ fail ERROR git-read-unavailable "working tree could not be read" "readable local Git facts" "Correct the Git or platform failure and retry." 5
146
+ fi
147
+ if [ -n "$working_tree" ]; then fail "ACTION REQUIRED" git-handoff-required "publication configuration needs normal Git review" "a clean synchronized public default branch" "Use the normal review, commit, PR, and merge flow; then rerun setup." 3; fi
148
+ branch=$(git symbolic-ref --quiet --short HEAD 2>/dev/null)
149
+ branch_status=$?
150
+ if [ "$branch_status" -eq 1 ]; then
151
+ fail "ACTION REQUIRED" git-handoff-required "HEAD is detached" "a checked-out public default branch" "Complete the normal Git handoff and rerun setup." 3
152
+ fi
153
+ if [ "$branch_status" -ne 0 ]; then
154
+ fail ERROR git-read-unavailable "current branch could not be read" "readable local Git facts" "Correct the Git or platform failure and retry." 5
155
+ fi
156
+ if ! remote=$(git remote get-url origin 2>/dev/null); then
157
+ fail ERROR git-read-unavailable "origin could not be read" "a readable public origin" "Correct the Git or platform failure and retry." 5
158
+ fi
159
+ if ! remote_listing=$(git ls-remote --symref origin HEAD 2>/dev/null); then
160
+ fail ERROR git-read-unavailable "remote default branch could not be read" "a reachable public origin" "Correct the Git or network failure and retry." 5
161
+ fi
162
+ remote_default=$(printf '%s\n' "$remote_listing" | sed -n 's#ref: refs/heads/\([^[:space:]]*\).*#\1#p' | head -n 1)
163
+ if [ -z "$remote_default" ]; then
164
+ fail "ACTION REQUIRED" git-handoff-required "remote default branch is unavailable" "a public default branch" "Complete the normal Git handoff and rerun setup." 3
165
+ fi
166
+ if ! remote_head=$(git ls-remote origin "refs/heads/$remote_default" 2>/dev/null); then
167
+ fail ERROR git-read-unavailable "remote default HEAD could not be read" "a reachable public origin" "Correct the Git or network failure and retry." 5
168
+ fi
169
+ remote_head=$(printf '%s\n' "$remote_head" | awk 'NR==1 {print $1}')
170
+ if ! local_head=$(git rev-parse HEAD 2>/dev/null); then
171
+ fail ERROR git-read-unavailable "local HEAD could not be read" "a readable local HEAD" "Correct the Git or platform failure and retry." 5
172
+ fi
173
+ if [ "$branch" != "$remote_default" ] || [ -z "$remote_head" ] || [ "$local_head" != "$remote_head" ]; then
174
+ fail "ACTION REQUIRED" git-handoff-required "branch or remote is not synchronized" "clean public default branch at its remote HEAD" "Complete the normal Git handoff and rerun setup." 3
175
+ fi
176
+ if ! REMOTE_URL="$remote" REPOSITORY_ROOT="$repository_root" node --conditions=source "$script_dir/bridge.ts" remote-matches-owner; then
177
+ fail ERROR repository-remote-conflict "$(printf '%s' "$remote" | redact)" "the public package owner GitHub repository" "Correct the normal Git remote and retry." 4
178
+ fi
179
+ printf 'OK git-handoff-complete\nSTAGE 4/9 Verify the first release artifact\n'
180
+ artifact_parent_input=${TMPDIR:-/tmp}
181
+ if ! artifact_parent=$(CDPATH='' cd -- "$artifact_parent_input" && pwd -P); then
182
+ fail ERROR artifact-temporary-output "temporary parent is unreadable" "a readable local temporary directory" "Correct TMPDIR permissions and retry." 5
183
+ fi
184
+ artifact_root=$(mktemp -d "$artifact_parent/npm-publication-setup-artifact.XXXXXX") || fail ERROR artifact-temporary-output "unable to create temporary output" "an empty local temporary directory" "Correct temporary directory permissions and retry." 5
185
+ artifact_cleaned=false
186
+ cleanup_artifact_root() {
187
+ [ "$artifact_cleaned" = true ] && return 0
188
+ case "$artifact_root" in
189
+ "$artifact_parent"/npm-publication-setup-artifact.*) ;;
190
+ *) return 1 ;;
191
+ esac
192
+ [ "$(dirname -- "$artifact_root")" = "$artifact_parent" ] || return 1
193
+ [ ! -e "$artifact_root" ] && [ ! -L "$artifact_root" ] || {
194
+ [ -d "$artifact_root" ] && [ ! -L "$artifact_root" ] || return 1
195
+ rm -rf -- "$artifact_root" || return 1
196
+ }
197
+ artifact_cleaned=true
198
+ }
199
+ cleanup_artifact_best_effort() {
200
+ cleanup_artifact_root >/dev/null 2>&1 || :
201
+ }
202
+ trap cleanup_artifact_best_effort EXIT HUP INT TERM
203
+ if [ "$debug" = true ]; then printf 'COMMAND %s\n' "verifyNpmPublicationArtifact [temporary output]"; fi
204
+ artifact_result=$(REPOSITORY_ROOT="$repository_root" ARTIFACT_OUTPUT_DIRECTORY="$artifact_root" node --conditions=source "$script_dir/bridge.ts" artifact)
205
+ artifact_status=$?
206
+ if [ "$artifact_status" -ne 0 ]; then
207
+ printf '%s\n' "$artifact_result" >&2
208
+ exit "$artifact_status"
209
+ fi
210
+ printf '%s\n' "$artifact_result" | sed '$d'
211
+ acceptance=$(printf '%s\n' "$artifact_result" | tail -n 1)
212
+ if [ "$non_interactive" = true ]; then fail "ACTION REQUIRED" artifact-acceptance-required "non-interactive mode cannot accept an artifact" "$acceptance" "Review the receipt and enter the exact acceptance interactively." 3; fi
213
+ printf '%s\n' "$acceptance"; printf 'Acceptance: '; read -r entered || fail "ACTION REQUIRED" artifact-acceptance-required "end of input" "$acceptance" "Review the receipt and enter the exact acceptance." 3
214
+ [ "$entered" = "$acceptance" ] || fail "ACTION REQUIRED" artifact-acceptance-required "acceptance did not match this receipt" "$acceptance" "Review the receipt and enter the exact acceptance." 3
215
+ # A successful exec is the ownership boundary: before it, this shell owns the
216
+ # accepted artifact; after it, the private bridge owns the artifact, session,
217
+ # signal handling, logout, and cleanup as one linear lifecycle.
218
+ REPOSITORY_ROOT="$repository_root" ARTIFACT_ROOT="$artifact_root" DEBUG="$debug" \
219
+ exec node --conditions=source "$script_dir/bridge.ts" external
220
+ exec_status=$?
221
+ fail ERROR external-bridge-unavailable "could not transfer the accepted artifact to the private setup bridge" "a runnable private setup bridge" "Correct the generated setup files and retry." "$exec_status"
@@ -0,0 +1,133 @@
1
+ export type CliCommandIdentity = {
2
+ readonly commandName: string;
3
+ readonly version: string;
4
+ };
5
+
6
+ const reservedSystemToolNames = new Set([
7
+ "aux",
8
+ "bash",
9
+ "bun",
10
+ "cmd",
11
+ "com1",
12
+ "com2",
13
+ "com3",
14
+ "com4",
15
+ "com5",
16
+ "com6",
17
+ "com7",
18
+ "com8",
19
+ "com9",
20
+ "con",
21
+ "corepack",
22
+ "deno",
23
+ "git",
24
+ "lpt1",
25
+ "lpt2",
26
+ "lpt3",
27
+ "lpt4",
28
+ "lpt5",
29
+ "lpt6",
30
+ "lpt7",
31
+ "lpt8",
32
+ "lpt9",
33
+ "node",
34
+ "nul",
35
+ "npm",
36
+ "npx",
37
+ "pnpm",
38
+ "powershell",
39
+ "prn",
40
+ "pwsh",
41
+ "sh",
42
+ "yarn",
43
+ "zsh",
44
+ ]);
45
+
46
+ export function validateCliCommandName(
47
+ commandName: string,
48
+ occupiedCommandNames: readonly string[] = [],
49
+ ): string {
50
+ if (/[/\\]/u.test(commandName)) {
51
+ throw new Error(
52
+ `CLI command name must not be a path; received ${JSON.stringify(commandName)}`,
53
+ );
54
+ }
55
+ if (/\p{White_Space}/u.test(commandName)) {
56
+ throw new Error(
57
+ `CLI command name must not contain whitespace; received ${JSON.stringify(commandName)}`,
58
+ );
59
+ }
60
+ if (/\p{Control}/u.test(commandName)) {
61
+ throw new Error(
62
+ `CLI command name must not contain a control character; received ${JSON.stringify(commandName)}`,
63
+ );
64
+ }
65
+ if (commandName.startsWith("-")) {
66
+ throw new Error(
67
+ `CLI command name must not have a leading hyphen; received ${JSON.stringify(commandName)}`,
68
+ );
69
+ }
70
+ if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/u.test(commandName)) {
71
+ throw new Error(
72
+ `CLI command name must use lowercase ASCII letters, digits, and single hyphens; received ${JSON.stringify(commandName)}`,
73
+ );
74
+ }
75
+ if (reservedSystemToolNames.has(commandName)) {
76
+ throw new Error(
77
+ `CLI command name is a reserved system tool; received ${JSON.stringify(commandName)}`,
78
+ );
79
+ }
80
+ if (occupiedCommandNames.includes(commandName)) {
81
+ throw new Error(
82
+ `CLI command name is already used by another workspace package; received ${JSON.stringify(commandName)}`,
83
+ );
84
+ }
85
+ return commandName;
86
+ }
87
+
88
+ export function cliCommandIdentity(
89
+ manifest: unknown,
90
+ occupiedCommandNames: readonly string[] = [],
91
+ ): CliCommandIdentity {
92
+ if (
93
+ typeof manifest !== "object" ||
94
+ manifest === null ||
95
+ Array.isArray(manifest)
96
+ ) {
97
+ throw new Error("CLI package manifest must be an object");
98
+ }
99
+ const manifestFacts = manifest as Readonly<Record<string, unknown>>;
100
+ if (
101
+ typeof manifestFacts.bin !== "object" ||
102
+ manifestFacts.bin === null ||
103
+ Array.isArray(manifestFacts.bin)
104
+ ) {
105
+ throw new Error("CLI package manifest must declare exactly one bin");
106
+ }
107
+ const binEntries = Object.entries(manifestFacts.bin);
108
+ if (binEntries.length !== 1) {
109
+ throw new Error("CLI package manifest must declare exactly one bin");
110
+ }
111
+ const [commandName, executable] = binEntries[0]!;
112
+ if (executable !== "./dist/cli.js") {
113
+ throw new Error('CLI package manifest bin must execute "./dist/cli.js"');
114
+ }
115
+
116
+ let version = "unpublished";
117
+ if (manifestFacts.version !== undefined) {
118
+ if (
119
+ typeof manifestFacts.version !== "string" ||
120
+ manifestFacts.version.length === 0
121
+ ) {
122
+ throw new Error(
123
+ "CLI package manifest version must be a non-empty string when present",
124
+ );
125
+ }
126
+ version = manifestFacts.version;
127
+ }
128
+
129
+ return {
130
+ commandName: validateCliCommandName(commandName, occupiedCommandNames),
131
+ version,
132
+ };
133
+ }
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from "node:module";
3
3
 
4
- import { runCli, type CliRuntime } from "#main";
4
+ import { cliCommandIdentity } from "./cli-command-identity.ts";
5
+ import { runCli, type CliRuntime } from "./main.ts";
5
6
 
6
7
  const require = createRequire(import.meta.url);
7
- const packageManifest = require("../package.json") as { version: string };
8
+ const packageManifest = require("../package.json") as unknown;
8
9
 
9
10
  const runtime: CliRuntime = {
10
11
  argv: process.argv,
@@ -20,7 +21,7 @@ const runtime: CliRuntime = {
20
21
  stdout: Boolean(process.stdout.isTTY),
21
22
  stderr: Boolean(process.stderr.isTTY),
22
23
  },
23
- version: packageManifest.version,
24
+ identity: cliCommandIdentity(packageManifest),
24
25
  };
25
26
 
26
27
  process.exitCode = await runCli(runtime);
@@ -1,6 +1,7 @@
1
1
  import { Command, CommanderError } from "commander";
2
2
 
3
- // @template-anchor cli-command-name
3
+ import type { CliCommandIdentity } from "./cli-command-identity.ts";
4
+
4
5
  export type Greeting = {
5
6
  readonly message: string;
6
7
  };
@@ -19,7 +20,7 @@ export type CliRuntime = {
19
20
  readonly stdout: boolean;
20
21
  readonly stderr: boolean;
21
22
  };
22
- readonly version: string;
23
+ readonly identity: CliCommandIdentity;
23
24
  };
24
25
 
25
26
  export function greet(name: string): Greeting {
@@ -36,9 +37,9 @@ function errorMessage(error: unknown): string {
36
37
 
37
38
  export function createCliCommand(runtime: CliRuntime): Command {
38
39
  const command = new Command()
39
- .name(commandName)
40
+ .name(runtime.identity.commandName)
40
41
  .description("A TypeScript command-line tool.")
41
- .version(runtime.version)
42
+ .version(runtime.identity.version)
42
43
  .configureOutput({
43
44
  writeOut: (text) => runtime.streams.stdout.write(text),
44
45
  writeErr: (text) => runtime.streams.stderr.write(text),
@@ -1,14 +1,15 @@
1
+ import { createRequire } from "node:module";
2
+
1
3
  import { describe, expect, it } from "vitest";
2
4
 
5
+ import { cliCommandIdentity } from "../../src/cli-command-identity.ts";
3
6
  import { runCli, type CliRuntime } from "../../src/main.ts";
4
7
 
5
- let commandName = "demo-cli";
6
- // @template-anchor cli-test-command-name
8
+ const require = createRequire(import.meta.url);
9
+ const packageManifest = require("../../package.json") as unknown;
10
+ const identity = cliCommandIdentity(packageManifest);
7
11
 
8
- function testRuntime(
9
- args: readonly string[],
10
- version = "1.2.3",
11
- ): {
12
+ function testRuntime(args: readonly string[]): {
12
13
  readonly runtime: CliRuntime;
13
14
  readonly stdout: () => string;
14
15
  readonly stderr: () => string;
@@ -17,7 +18,7 @@ function testRuntime(
17
18
  let stderr = "";
18
19
  return {
19
20
  runtime: {
20
- argv: ["node", commandName, ...args],
21
+ argv: ["node", identity.commandName, ...args],
21
22
  streams: {
22
23
  stdin: {},
23
24
  stdout: { write: (chunk) => (stdout += chunk) },
@@ -26,7 +27,7 @@ function testRuntime(
26
27
  cwd: "/workspace",
27
28
  env: { MODE: "test" },
28
29
  tty: { stdin: false, stdout: false, stderr: false },
29
- version,
30
+ identity,
30
31
  },
31
32
  stdout: () => stdout,
32
33
  stderr: () => stderr,
@@ -42,11 +43,11 @@ describe("CLI command control", () => {
42
43
  expect(output.stderr()).toBe("");
43
44
  });
44
45
 
45
- it("renders the injected package version", async () => {
46
- const output = testRuntime(["--version"], "9.8.7");
46
+ it("renders the version derived from the package manifest", async () => {
47
+ const output = testRuntime(["--version"]);
47
48
 
48
49
  await expect(runCli(output.runtime)).resolves.toBe(0);
49
- expect(output.stdout()).toBe("9.8.7\n");
50
+ expect(output.stdout()).toBe(`${identity.version}\n`);
50
51
  expect(output.stderr()).toBe("");
51
52
  });
52
53
 
@@ -55,7 +56,7 @@ describe("CLI command control", () => {
55
56
 
56
57
  await expect(runCli(output.runtime)).resolves.toBe(0);
57
58
  expect(output.stdout()).toContain(
58
- `Usage: ${commandName} [options] [command]`,
59
+ `Usage: ${identity.commandName} [options] [command]`,
59
60
  );
60
61
  expect(output.stdout()).toContain("greet <name>");
61
62
  expect(output.stderr()).toBe("");
@@ -69,7 +70,7 @@ describe("CLI command control", () => {
69
70
  expect(output.stderr()).toContain(
70
71
  "error: missing required argument 'name'",
71
72
  );
72
- expect(output.stderr()).toContain(`Usage: ${commandName} greet`);
73
+ expect(output.stderr()).toContain(`Usage: ${identity.commandName} greet`);
73
74
  });
74
75
 
75
76
  it("adapts business validation into a testable command error", async () => {
@@ -0,0 +1,76 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ cliCommandIdentity,
5
+ validateCliCommandName,
6
+ } from "../../src/cli-command-identity.ts";
7
+
8
+ describe("CLI command identity", () => {
9
+ it.each([
10
+ { name: "path separator", command: "tools/release", message: "path" },
11
+ { name: "backslash path", command: "tools\\release", message: "path" },
12
+ { name: "whitespace", command: "release tool", message: "whitespace" },
13
+ { name: "control character", command: "release\u0000", message: "control" },
14
+ { name: "leading hyphen", command: "-release", message: "leading hyphen" },
15
+ { name: "uppercase ASCII", command: "Release", message: "lowercase ASCII" },
16
+ { name: "non-ASCII", command: "发布", message: "lowercase ASCII" },
17
+ { name: "system tool", command: "node", message: "reserved system tool" },
18
+ { name: "Windows device", command: "con", message: "reserved system tool" },
19
+ {
20
+ name: "Windows null device",
21
+ command: "nul",
22
+ message: "reserved system tool",
23
+ },
24
+ {
25
+ name: "Windows serial device",
26
+ command: "com1",
27
+ message: "reserved system tool",
28
+ },
29
+ {
30
+ name: "Windows printer device",
31
+ command: "lpt9",
32
+ message: "reserved system tool",
33
+ },
34
+ ])("rejects an unsafe command name: $name", ({ command, message }) => {
35
+ expect(() => validateCliCommandName(command)).toThrow(message);
36
+ });
37
+
38
+ it("accepts a portable name unless another workspace command occupies it", () => {
39
+ expect(validateCliCommandName("release-tool", [])).toBe("release-tool");
40
+ expect(() =>
41
+ validateCliCommandName("release-tool", ["release-tool"]),
42
+ ).toThrow("already used by another workspace package");
43
+ });
44
+
45
+ it("derives command and unpublished version only from manifest facts", () => {
46
+ expect(cliCommandIdentity({ bin: { release: "./dist/cli.js" } })).toEqual({
47
+ commandName: "release",
48
+ version: "unpublished",
49
+ });
50
+ expect(
51
+ cliCommandIdentity({
52
+ bin: { ship: "./dist/cli.js" },
53
+ version: "2.3.4",
54
+ }),
55
+ ).toEqual({ commandName: "ship", version: "2.3.4" });
56
+ });
57
+
58
+ it("rejects malformed manifest identity facts", () => {
59
+ expect(() => cliCommandIdentity(null)).toThrow("must be an object");
60
+ expect(() => cliCommandIdentity({ bin: {} })).toThrow("exactly one bin");
61
+ expect(() =>
62
+ cliCommandIdentity({
63
+ bin: { release: "./dist/cli.js", ship: "./dist/cli.js" },
64
+ }),
65
+ ).toThrow("exactly one bin");
66
+ expect(() =>
67
+ cliCommandIdentity({ bin: { release: "./src/cli.ts" } }),
68
+ ).toThrow('must execute "./dist/cli.js"');
69
+ expect(() =>
70
+ cliCommandIdentity({
71
+ bin: { release: "./dist/cli.js" },
72
+ version: 1,
73
+ }),
74
+ ).toThrow("version must be a non-empty string");
75
+ });
76
+ });
@@ -2,6 +2,8 @@
2
2
  "extends": "./tsconfig.json",
3
3
  "compilerOptions": {
4
4
  "customConditions": [],
5
+ "declaration": false,
6
+ "declarationMap": false,
5
7
  "outDir": "dist",
6
8
  "rootDir": "src",
7
9
  "types": ["node"]
@@ -13,7 +13,7 @@ export function assertDatabaseReady(db: Database) {
13
13
  throw new Error(
14
14
  [
15
15
  "Database is not ready.",
16
- "For local development, run `pnpm --dir packages/db-migrations run db:prepare:dev`.",
16
+ "For local development, run `pnpm --dir apps/web dev`.",
17
17
  "For deployment, run `pnpm --dir packages/db-migrations run db:prepare:deploy` before starting the app.",
18
18
  ].join(" "),
19
19
  { cause },
@@ -0,0 +1,21 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { rmSync } from "node:fs";
3
+ import path from "node:path";
4
+
5
+ const packageRoot = path.resolve(import.meta.dirname, "..");
6
+ const migrationsRoot = path.resolve(packageRoot, "../db-migrations");
7
+ const databaseFile = path.join(packageRoot, "node_modules/.tmp/test.sqlite");
8
+
9
+ export function setup(): void {
10
+ rmSync(databaseFile, { force: true });
11
+ process.env.DATABASE_FILE = databaseFile;
12
+ execFileSync("pnpm", ["run", "db:push"], {
13
+ cwd: migrationsRoot,
14
+ env: process.env,
15
+ stdio: "inherit",
16
+ });
17
+ }
18
+
19
+ export function teardown(): void {
20
+ rmSync(databaseFile, { force: true });
21
+ }
@@ -13,5 +13,10 @@
13
13
  "target": "es2023",
14
14
  "types": ["node"]
15
15
  },
16
- "include": ["src/**/*.ts", "scripts/**/*.ts", "test/**/*.ts"]
16
+ "include": [
17
+ "src/**/*.ts",
18
+ "scripts/**/*.ts",
19
+ "test/**/*.ts",
20
+ "vitest.config.ts"
21
+ ]
17
22
  }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globalSetup: ["./test/global-setup.ts"],
6
+ },
7
+ });
@@ -1,3 +1,5 @@
1
+ import { mkdirSync } from "node:fs";
2
+ import path from "node:path";
1
3
  import { fileURLToPath } from "node:url";
2
4
 
3
5
  import { defineConfig } from "drizzle-kit";
@@ -6,12 +8,14 @@ const databasePackageName = process.env.DATABASE_PACKAGE_NAME ?? "@database";
6
8
  const schemaFile = fileURLToPath(
7
9
  import.meta.resolve(`${databasePackageName}/schema`),
8
10
  );
11
+ const databaseFile = process.env.DATABASE_FILE ?? "./data/app.sqlite";
12
+ mkdirSync(path.dirname(databaseFile), { recursive: true });
9
13
 
10
14
  export default defineConfig({
11
15
  dialect: "sqlite",
12
16
  schema: schemaFile,
13
17
  out: "./drizzle/migrations",
14
18
  dbCredentials: {
15
- url: process.env.DATABASE_FILE ?? "./data/app.sqlite",
19
+ url: databaseFile,
16
20
  },
17
21
  });