fixdevcontainer 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.
- package/.depcheckrc.json +3 -0
- package/.github/workflows/add-reviewer.yml +15 -0
- package/.github/workflows/nodejs-ci.yml +208 -0
- package/.github/workflows/release.yml +84 -0
- package/.node-version +1 -0
- package/LICENSE +21 -0
- package/fixdevcontainer.js +126 -0
- package/fixdevcontainer.test.js +264 -0
- package/package.json +18 -0
- package/renovate.json +3 -0
package/.depcheckrc.json
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# Node.js でビルド・テストを実行する。バージョンは .node-version に記載されているものを利用する
|
|
2
|
+
|
|
3
|
+
name: Node CI
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
branches:
|
|
8
|
+
- main
|
|
9
|
+
- master
|
|
10
|
+
pull_request:
|
|
11
|
+
branches:
|
|
12
|
+
- main
|
|
13
|
+
- master
|
|
14
|
+
types:
|
|
15
|
+
- opened
|
|
16
|
+
- synchronize
|
|
17
|
+
pull_request_target:
|
|
18
|
+
branches:
|
|
19
|
+
- main
|
|
20
|
+
- master
|
|
21
|
+
types:
|
|
22
|
+
- labeled
|
|
23
|
+
workflow_dispatch:
|
|
24
|
+
schedule:
|
|
25
|
+
- cron: '0 0 * * *'
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
node-ci:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
if: ${{
|
|
31
|
+
github.event_name == 'push' ||
|
|
32
|
+
github.event_name == 'workflow_dispatch' ||
|
|
33
|
+
github.event_name == 'schedule' ||
|
|
34
|
+
(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == false) ||
|
|
35
|
+
(github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.fork == true && contains(github.event.label.name, '🚀request-ci'))
|
|
36
|
+
}}
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: 🛎 Checkout
|
|
40
|
+
uses: actions/checkout@v4
|
|
41
|
+
with:
|
|
42
|
+
ref: ${{ github.event.pull_request.head.sha }}
|
|
43
|
+
|
|
44
|
+
- name: 🏗 Setup node
|
|
45
|
+
uses: actions/setup-node@v4
|
|
46
|
+
with:
|
|
47
|
+
node-version-file: .node-version
|
|
48
|
+
|
|
49
|
+
- name: Setup pnpm
|
|
50
|
+
uses: pnpm/action-setup@v2
|
|
51
|
+
id: pnpm-install
|
|
52
|
+
with:
|
|
53
|
+
run_install: false
|
|
54
|
+
|
|
55
|
+
- name: 📃 Check package.json definition
|
|
56
|
+
id: package-json
|
|
57
|
+
run: |
|
|
58
|
+
compile=$(jq '.scripts | has("compile")' package.json)
|
|
59
|
+
build=$(jq '.scripts | has("build")' package.json)
|
|
60
|
+
generate=$(jq '.scripts | has("generate")' package.json)
|
|
61
|
+
package=$(jq '.scripts | has("package")' package.json)
|
|
62
|
+
lint=$(jq '.scripts | has("lint")' package.json)
|
|
63
|
+
test=$(jq '.scripts | has("test")' package.json)
|
|
64
|
+
|
|
65
|
+
echo "compile: $compile"
|
|
66
|
+
echo "build: $build"
|
|
67
|
+
echo "generate: $generate"
|
|
68
|
+
echo "package: $package"
|
|
69
|
+
echo "lint: $lint"
|
|
70
|
+
echo "test: $test"
|
|
71
|
+
|
|
72
|
+
echo "compile=$compile" >> $GITHUB_OUTPUT
|
|
73
|
+
echo "build=$build" >> $GITHUB_OUTPUT
|
|
74
|
+
echo "generate=$generate" >> $GITHUB_OUTPUT
|
|
75
|
+
echo "package=$package" >> $GITHUB_OUTPUT
|
|
76
|
+
echo "lint=$lint" >> $GITHUB_OUTPUT
|
|
77
|
+
echo "test=$test" >> $GITHUB_OUTPUT
|
|
78
|
+
|
|
79
|
+
- name: 📂 Get pnpm store directory
|
|
80
|
+
id: pnpm-cache
|
|
81
|
+
shell: bash
|
|
82
|
+
run: |
|
|
83
|
+
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
84
|
+
|
|
85
|
+
- name: 📂 Setup pnpm cache
|
|
86
|
+
uses: actions/cache@v4
|
|
87
|
+
with:
|
|
88
|
+
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
89
|
+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
90
|
+
restore-keys: |
|
|
91
|
+
${{ runner.os }}-pnpm-store-
|
|
92
|
+
|
|
93
|
+
- name: 👨🏻💻 Install dependencies
|
|
94
|
+
run: |
|
|
95
|
+
pnpm install --frozen-lockfile --prefer-frozen-lockfile
|
|
96
|
+
|
|
97
|
+
- name: 👀 Run linter
|
|
98
|
+
if: steps.package-json.outputs.lint == 'true'
|
|
99
|
+
run: pnpm run lint
|
|
100
|
+
|
|
101
|
+
- name: 🎁 Run package
|
|
102
|
+
if: steps.package-json.outputs.package == 'true'
|
|
103
|
+
run: pnpm run package
|
|
104
|
+
|
|
105
|
+
- name: 🏃 Run compile
|
|
106
|
+
if: steps.package-json.outputs.compile == 'true'
|
|
107
|
+
run: pnpm run compile
|
|
108
|
+
|
|
109
|
+
- name: 🧪 Run tests
|
|
110
|
+
uses: coactions/setup-xvfb@v1
|
|
111
|
+
if: steps.package-json.outputs.test == 'true'
|
|
112
|
+
with:
|
|
113
|
+
run: pnpm run test
|
|
114
|
+
|
|
115
|
+
- name: ☑️ Check Dependencies
|
|
116
|
+
if: steps.package-json.outputs.disabled-depcheck == 'false'
|
|
117
|
+
run: npx depcheck
|
|
118
|
+
|
|
119
|
+
- name: Check exists dist directory
|
|
120
|
+
id: check-dist
|
|
121
|
+
run: |
|
|
122
|
+
IS_DIRECTORY=$(test -d dist && echo true || echo false)
|
|
123
|
+
IS_SYMLINK=$(test -L dist && echo true || echo false)
|
|
124
|
+
echo "exists=$(test $IS_DIRECTORY = true && $IS_NOT_SYMLINK = false && echo true || echo false)" >> $GITHUB_OUTPUT
|
|
125
|
+
|
|
126
|
+
- name: 📦 Upload dist artifact
|
|
127
|
+
if: steps.check-dist.outputs.exists == 'true'
|
|
128
|
+
uses: actions/upload-artifact@v4
|
|
129
|
+
with:
|
|
130
|
+
name: dist
|
|
131
|
+
path: dist
|
|
132
|
+
|
|
133
|
+
- name: Check exists output directory
|
|
134
|
+
id: check-output
|
|
135
|
+
run: |
|
|
136
|
+
IS_DIRECTORY=$(test -d output && echo true || echo false)
|
|
137
|
+
IS_SYMLINK=$(test -L output && echo true || echo false)
|
|
138
|
+
echo "exists=$(test $IS_DIRECTORY = true && $IS_NOT_SYMLINK = false && echo true || echo false)" >> $GITHUB_OUTPUT
|
|
139
|
+
|
|
140
|
+
- name: 📦 Upload output artifact
|
|
141
|
+
if: steps.check-output.outputs.exists == 'true'
|
|
142
|
+
uses: actions/upload-artifact@v4
|
|
143
|
+
with:
|
|
144
|
+
name: output
|
|
145
|
+
path: output
|
|
146
|
+
|
|
147
|
+
- name: 👀 Check git status
|
|
148
|
+
run: |
|
|
149
|
+
git status
|
|
150
|
+
git diff --exit-code || (echo "Git status is not clean." && exit 1)
|
|
151
|
+
|
|
152
|
+
comment:
|
|
153
|
+
name: Comment
|
|
154
|
+
runs-on: ubuntu-latest
|
|
155
|
+
|
|
156
|
+
if: ${{
|
|
157
|
+
github.event_name == 'pull_request_target' &&
|
|
158
|
+
github.event.action == 'opened' &&
|
|
159
|
+
github.repository != github.event.pull_request.head.repo.full_name
|
|
160
|
+
}}
|
|
161
|
+
|
|
162
|
+
steps:
|
|
163
|
+
- name: Create PR comment
|
|
164
|
+
run: |
|
|
165
|
+
cat << EOF > comment.md
|
|
166
|
+
# ⚠️ CIの実行には \`🚀request-ci\` ラベルが必要です
|
|
167
|
+
|
|
168
|
+
このリポジトリはフォークされたリポジトリです。
|
|
169
|
+
セキュリティ上の理由から、フォークされたリポジトリからのCI実行は自動的に行われません。
|
|
170
|
+
|
|
171
|
+
CI実行をリクエストするには、このプルリクエストに \`🚀request-ci\` ラベルを追加してください。
|
|
172
|
+
(ラベルを追加できるのは一部のメンバーのみです)
|
|
173
|
+
EOF
|
|
174
|
+
|
|
175
|
+
gh pr comment ${{ github.event.number }} -R ${{ github.repository }} -F comment.md
|
|
176
|
+
env:
|
|
177
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
178
|
+
|
|
179
|
+
finished-node-ci:
|
|
180
|
+
name: Check finished Node CI
|
|
181
|
+
runs-on: ubuntu-latest
|
|
182
|
+
if: always()
|
|
183
|
+
needs:
|
|
184
|
+
- node-ci
|
|
185
|
+
|
|
186
|
+
steps:
|
|
187
|
+
- name: Workflow conclusion
|
|
188
|
+
uses: technote-space/workflow-conclusion-action@v3
|
|
189
|
+
|
|
190
|
+
- name: Check finished Node CI
|
|
191
|
+
run: |
|
|
192
|
+
if [ "${{ env.WORKFLOW_CONCLUSION }}" != "success" ]; then
|
|
193
|
+
echo "Build failed"
|
|
194
|
+
exit 1
|
|
195
|
+
fi
|
|
196
|
+
|
|
197
|
+
- name: Remove label
|
|
198
|
+
if: ${{ github.event_name == 'pull_request_target' && contains(github.event.label.name, '🚀request-ci') }}
|
|
199
|
+
uses: actions/github-script@v7
|
|
200
|
+
with:
|
|
201
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
202
|
+
script: |
|
|
203
|
+
github.rest.issues.removeLabel({
|
|
204
|
+
issue_number: context.issue.number,
|
|
205
|
+
owner: context.repo.owner,
|
|
206
|
+
repo: context.repo.repo,
|
|
207
|
+
name: ['🚀request-ci']
|
|
208
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request_target:
|
|
5
|
+
branches:
|
|
6
|
+
- main
|
|
7
|
+
- master
|
|
8
|
+
types:
|
|
9
|
+
- closed
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: ${{ github.workflow }}
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
release:
|
|
16
|
+
name: Release
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
if: github.event.pull_request.merged == true
|
|
19
|
+
|
|
20
|
+
steps:
|
|
21
|
+
- name: 🛎 Checkout
|
|
22
|
+
uses: actions/checkout@v4
|
|
23
|
+
with:
|
|
24
|
+
fetch-depth: 0
|
|
25
|
+
|
|
26
|
+
- name: 🏗 Setup node
|
|
27
|
+
uses: actions/setup-node@v4
|
|
28
|
+
with:
|
|
29
|
+
node-version-file: .node-version
|
|
30
|
+
registry-url: 'https://registry.npmjs.org'
|
|
31
|
+
|
|
32
|
+
- name: Setup pnpm
|
|
33
|
+
uses: pnpm/action-setup@v2
|
|
34
|
+
id: pnpm-install
|
|
35
|
+
with:
|
|
36
|
+
run_install: false
|
|
37
|
+
|
|
38
|
+
- name: 📂 Get pnpm store directory
|
|
39
|
+
id: pnpm-cache
|
|
40
|
+
shell: bash
|
|
41
|
+
run: |
|
|
42
|
+
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
43
|
+
|
|
44
|
+
- name: 📂 Setup pnpm cache
|
|
45
|
+
uses: actions/cache@v4
|
|
46
|
+
with:
|
|
47
|
+
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
|
48
|
+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
49
|
+
restore-keys: |
|
|
50
|
+
${{ runner.os }}-pnpm-store-
|
|
51
|
+
|
|
52
|
+
- name: 👨🏻💻 Install dependencies
|
|
53
|
+
run: |
|
|
54
|
+
pnpm install --frozen-lockfile --prefer-frozen-lockfile
|
|
55
|
+
|
|
56
|
+
- name: 🏷 Bump version and push tag
|
|
57
|
+
id: tag-version
|
|
58
|
+
uses: mathieudutour/github-tag-action@v6.2
|
|
59
|
+
with:
|
|
60
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
61
|
+
default_bump: 'minor'
|
|
62
|
+
custom_release_rules: 'feat:minor:✨ Features,fix:patch:🐛 Fixes,docs:patch:📰 Docs,chore:patch:🎨 Chore,pref:patch:🎈 Performance improvements,refactor:patch:🧹 Refactoring,build:patch:🔍 Build,ci:patch:🔍 CI,revert:patch:⏪ Revert,style:patch:🧹 Style,test:patch:👀 Test,release:major:📦 Release'
|
|
63
|
+
|
|
64
|
+
- name: 📝 Update package.json version
|
|
65
|
+
run: |
|
|
66
|
+
pnpm version --no-git-tag-version ${{ steps.tag-version.outputs.new_version }}
|
|
67
|
+
|
|
68
|
+
- name: 📦 Publish
|
|
69
|
+
run: |
|
|
70
|
+
pnpm publish --access public --no-git-checks
|
|
71
|
+
env:
|
|
72
|
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
73
|
+
|
|
74
|
+
- name: 📃 Create Release
|
|
75
|
+
id: create_release
|
|
76
|
+
uses: ncipollo/release-action@v1.14.0
|
|
77
|
+
env:
|
|
78
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
79
|
+
with:
|
|
80
|
+
tag: ${{ steps.tag-version.outputs.new_tag }}
|
|
81
|
+
name: ${{ steps.tag-version.outputs.new_tag }}
|
|
82
|
+
body: ${{ steps.tag-version.outputs.changelog }}
|
|
83
|
+
draft: false
|
|
84
|
+
prerelease: false
|
package/.node-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
20.12.2
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Tomachi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
const predefinedOrder = [
|
|
8
|
+
// 基本情報
|
|
9
|
+
"$schema",
|
|
10
|
+
"name",
|
|
11
|
+
// コンテナイメージ・構成
|
|
12
|
+
"image",
|
|
13
|
+
"dockerComposeFile",
|
|
14
|
+
"service",
|
|
15
|
+
"runServices",
|
|
16
|
+
"build",
|
|
17
|
+
// 動作設定
|
|
18
|
+
"shutdownAction",
|
|
19
|
+
"overrideCommand",
|
|
20
|
+
"updateRemoteUserUID",
|
|
21
|
+
"init",
|
|
22
|
+
"privileged",
|
|
23
|
+
// ユーザーと環境設定
|
|
24
|
+
"containerUser",
|
|
25
|
+
"containerEnv",
|
|
26
|
+
"remoteUser",
|
|
27
|
+
"remoteEnv",
|
|
28
|
+
// ポートとネットワーク設定
|
|
29
|
+
"forwardPorts",
|
|
30
|
+
"portsAttributes",
|
|
31
|
+
"otherPortsAttributes",
|
|
32
|
+
// 機能とシークレット
|
|
33
|
+
"features",
|
|
34
|
+
"overrideFeatureInstallOrder",
|
|
35
|
+
"secrets",
|
|
36
|
+
// コマンド実行と待機設定
|
|
37
|
+
"initializeCommand",
|
|
38
|
+
"onCreateCommand",
|
|
39
|
+
"updateContentCommand",
|
|
40
|
+
"postCreateCommand",
|
|
41
|
+
"postStartCommand",
|
|
42
|
+
"postAttachCommand",
|
|
43
|
+
"waitFor",
|
|
44
|
+
// ワークスペースとマウント設定
|
|
45
|
+
"workspaceFolder",
|
|
46
|
+
"appPort",
|
|
47
|
+
"runArgs",
|
|
48
|
+
"workspaceMount",
|
|
49
|
+
// マウント詳細設定
|
|
50
|
+
"type",
|
|
51
|
+
"source",
|
|
52
|
+
"target",
|
|
53
|
+
// ユーザー環境プローブ
|
|
54
|
+
"userEnvProbe",
|
|
55
|
+
// ホスト要件とカスタマイズ
|
|
56
|
+
"hostRequirements",
|
|
57
|
+
"customizations",
|
|
58
|
+
// その他のプロパティ
|
|
59
|
+
"additionalProperties",
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
function loadJson(filename) {
|
|
63
|
+
if (!fs.existsSync(filename)) {
|
|
64
|
+
console.error(`\u001B[31mError: File not found: ${filename}\u001B[0m`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let inputData;
|
|
69
|
+
try {
|
|
70
|
+
inputData = fs.readFileSync(filename, "utf8");
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(`\u001B[31mError reading file: ${error}\u001B[0m`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let jsonData;
|
|
77
|
+
try {
|
|
78
|
+
jsonData = JSON.parse(inputData);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error(`\u001B[31mError parsing JSON: ${error}\u001B[0m`);
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return jsonData;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function formatter(filename) {
|
|
88
|
+
const jsonData = loadJson(filename);
|
|
89
|
+
if (!jsonData) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const sortedJson = {};
|
|
94
|
+
for (const key of predefinedOrder) {
|
|
95
|
+
if (key in jsonData) {
|
|
96
|
+
sortedJson[key] = jsonData[key];
|
|
97
|
+
delete jsonData[key];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle undefined keys by appending them at the end in alphabetical order
|
|
102
|
+
for (const key of Object.keys(jsonData).sort()) {
|
|
103
|
+
console.warn(
|
|
104
|
+
`\u001B[33mWarning: Undefined key '${key}' found in JSON\u001B[0m`
|
|
105
|
+
); // Yellow text for warnings
|
|
106
|
+
sortedJson[key] = jsonData[key];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const outputData = JSON.stringify(sortedJson, null, 2);
|
|
110
|
+
try {
|
|
111
|
+
fs.writeFileSync(filename, outputData, "utf8");
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`\u001B[31mError writing file: ${error}\u001B[0m`);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.log(
|
|
118
|
+
`\u001B[32mSuccessfully sorted and written back to ${filename}\u001B[0m`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
exports.formatter = formatter;
|
|
123
|
+
|
|
124
|
+
const filename =
|
|
125
|
+
process.argv[2] || path.join(".devcontainer", "devcontainer.json");
|
|
126
|
+
formatter(filename);
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
const fs = require("node:fs");
|
|
2
|
+
const { formatter } = require("./fixdevcontainer");
|
|
3
|
+
|
|
4
|
+
// Test case 1: Valid JSON input
|
|
5
|
+
const input1 = {
|
|
6
|
+
name: "My Dev Container",
|
|
7
|
+
image: "my-dev-container",
|
|
8
|
+
workspaceFolder: "/workspace",
|
|
9
|
+
additionalProperties: {
|
|
10
|
+
prop1: "value1",
|
|
11
|
+
prop2: "value2",
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
const expectedOutput1 = {
|
|
15
|
+
$schema: undefined,
|
|
16
|
+
name: "My Dev Container",
|
|
17
|
+
image: "my-dev-container",
|
|
18
|
+
dockerComposeFile: undefined,
|
|
19
|
+
service: undefined,
|
|
20
|
+
runServices: undefined,
|
|
21
|
+
build: undefined,
|
|
22
|
+
shutdownAction: undefined,
|
|
23
|
+
overrideCommand: undefined,
|
|
24
|
+
updateRemoteUserUID: undefined,
|
|
25
|
+
init: undefined,
|
|
26
|
+
privileged: undefined,
|
|
27
|
+
containerUser: undefined,
|
|
28
|
+
containerEnv: undefined,
|
|
29
|
+
remoteUser: undefined,
|
|
30
|
+
remoteEnv: undefined,
|
|
31
|
+
forwardPorts: undefined,
|
|
32
|
+
portsAttributes: undefined,
|
|
33
|
+
otherPortsAttributes: undefined,
|
|
34
|
+
features: undefined,
|
|
35
|
+
overrideFeatureInstallOrder: undefined,
|
|
36
|
+
secrets: undefined,
|
|
37
|
+
initializeCommand: undefined,
|
|
38
|
+
onCreateCommand: undefined,
|
|
39
|
+
updateContentCommand: undefined,
|
|
40
|
+
postCreateCommand: undefined,
|
|
41
|
+
postStartCommand: undefined,
|
|
42
|
+
postAttachCommand: undefined,
|
|
43
|
+
waitFor: undefined,
|
|
44
|
+
workspaceFolder: "/workspace",
|
|
45
|
+
appPort: undefined,
|
|
46
|
+
runArgs: undefined,
|
|
47
|
+
workspaceMount: undefined,
|
|
48
|
+
type: undefined,
|
|
49
|
+
source: undefined,
|
|
50
|
+
target: undefined,
|
|
51
|
+
userEnvProbe: undefined,
|
|
52
|
+
hostRequirements: undefined,
|
|
53
|
+
customizations: undefined,
|
|
54
|
+
additionalProperties: {
|
|
55
|
+
prop1: "value1",
|
|
56
|
+
prop2: "value2",
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// Test case 2: JSON input with undefined keys
|
|
61
|
+
const input2 = {
|
|
62
|
+
name: "My Dev Container",
|
|
63
|
+
image: "my-dev-container",
|
|
64
|
+
undefinedKey1: "value1",
|
|
65
|
+
undefinedKey2: "value2",
|
|
66
|
+
};
|
|
67
|
+
const expectedOutput2 = {
|
|
68
|
+
$schema: undefined,
|
|
69
|
+
name: "My Dev Container",
|
|
70
|
+
image: "my-dev-container",
|
|
71
|
+
dockerComposeFile: undefined,
|
|
72
|
+
service: undefined,
|
|
73
|
+
runServices: undefined,
|
|
74
|
+
build: undefined,
|
|
75
|
+
shutdownAction: undefined,
|
|
76
|
+
overrideCommand: undefined,
|
|
77
|
+
updateRemoteUserUID: undefined,
|
|
78
|
+
init: undefined,
|
|
79
|
+
privileged: undefined,
|
|
80
|
+
containerUser: undefined,
|
|
81
|
+
containerEnv: undefined,
|
|
82
|
+
remoteUser: undefined,
|
|
83
|
+
remoteEnv: undefined,
|
|
84
|
+
forwardPorts: undefined,
|
|
85
|
+
portsAttributes: undefined,
|
|
86
|
+
otherPortsAttributes: undefined,
|
|
87
|
+
features: undefined,
|
|
88
|
+
overrideFeatureInstallOrder: undefined,
|
|
89
|
+
secrets: undefined,
|
|
90
|
+
initializeCommand: undefined,
|
|
91
|
+
onCreateCommand: undefined,
|
|
92
|
+
updateContentCommand: undefined,
|
|
93
|
+
postCreateCommand: undefined,
|
|
94
|
+
postStartCommand: undefined,
|
|
95
|
+
postAttachCommand: undefined,
|
|
96
|
+
waitFor: undefined,
|
|
97
|
+
workspaceFolder: undefined,
|
|
98
|
+
appPort: undefined,
|
|
99
|
+
runArgs: undefined,
|
|
100
|
+
workspaceMount: undefined,
|
|
101
|
+
type: undefined,
|
|
102
|
+
source: undefined,
|
|
103
|
+
target: undefined,
|
|
104
|
+
userEnvProbe: undefined,
|
|
105
|
+
hostRequirements: undefined,
|
|
106
|
+
customizations: undefined,
|
|
107
|
+
additionalProperties: undefined,
|
|
108
|
+
undefinedKey1: "value1",
|
|
109
|
+
undefinedKey2: "value2",
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Test case 3: Empty JSON input
|
|
113
|
+
const input3 = {};
|
|
114
|
+
const expectedOutput3 = {
|
|
115
|
+
$schema: undefined,
|
|
116
|
+
name: undefined,
|
|
117
|
+
image: undefined,
|
|
118
|
+
dockerComposeFile: undefined,
|
|
119
|
+
service: undefined,
|
|
120
|
+
runServices: undefined,
|
|
121
|
+
build: undefined,
|
|
122
|
+
shutdownAction: undefined,
|
|
123
|
+
overrideCommand: undefined,
|
|
124
|
+
updateRemoteUserUID: undefined,
|
|
125
|
+
init: undefined,
|
|
126
|
+
privileged: undefined,
|
|
127
|
+
containerUser: undefined,
|
|
128
|
+
containerEnv: undefined,
|
|
129
|
+
remoteUser: undefined,
|
|
130
|
+
remoteEnv: undefined,
|
|
131
|
+
forwardPorts: undefined,
|
|
132
|
+
portsAttributes: undefined,
|
|
133
|
+
otherPortsAttributes: undefined,
|
|
134
|
+
features: undefined,
|
|
135
|
+
overrideFeatureInstallOrder: undefined,
|
|
136
|
+
secrets: undefined,
|
|
137
|
+
initializeCommand: undefined,
|
|
138
|
+
onCreateCommand: undefined,
|
|
139
|
+
updateContentCommand: undefined,
|
|
140
|
+
postCreateCommand: undefined,
|
|
141
|
+
postStartCommand: undefined,
|
|
142
|
+
postAttachCommand: undefined,
|
|
143
|
+
waitFor: undefined,
|
|
144
|
+
workspaceFolder: undefined,
|
|
145
|
+
appPort: undefined,
|
|
146
|
+
runArgs: undefined,
|
|
147
|
+
workspaceMount: undefined,
|
|
148
|
+
type: undefined,
|
|
149
|
+
source: undefined,
|
|
150
|
+
target: undefined,
|
|
151
|
+
userEnvProbe: undefined,
|
|
152
|
+
hostRequirements: undefined,
|
|
153
|
+
customizations: undefined,
|
|
154
|
+
additionalProperties: undefined,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
// Test case 4: Invalid JSON input
|
|
158
|
+
const input4 = "invalid json";
|
|
159
|
+
const expectedOutput4 = undefined;
|
|
160
|
+
|
|
161
|
+
// Mock the fs.readFileSync and fs.writeFileSync functions
|
|
162
|
+
jest.mock("node:fs", () => ({
|
|
163
|
+
existsSync: jest.fn((filename) => {
|
|
164
|
+
return (
|
|
165
|
+
filename === "valid.json" ||
|
|
166
|
+
filename === "undefined_keys.json" ||
|
|
167
|
+
filename === "empty.json" ||
|
|
168
|
+
filename === "invalid.json"
|
|
169
|
+
);
|
|
170
|
+
}),
|
|
171
|
+
readFileSync: jest.fn((filename, encoding) => {
|
|
172
|
+
if (filename === "valid.json") {
|
|
173
|
+
return JSON.stringify(input1);
|
|
174
|
+
} else if (filename === "undefined_keys.json") {
|
|
175
|
+
return JSON.stringify(input2);
|
|
176
|
+
} else if (filename === "empty.json") {
|
|
177
|
+
return JSON.stringify(input3);
|
|
178
|
+
} else if (filename === "invalid.json") {
|
|
179
|
+
return input4;
|
|
180
|
+
}
|
|
181
|
+
}),
|
|
182
|
+
writeFileSync: jest.fn((filename, data, encoding) => {}),
|
|
183
|
+
}));
|
|
184
|
+
|
|
185
|
+
// Mock the console.log function
|
|
186
|
+
console.log = jest.fn();
|
|
187
|
+
|
|
188
|
+
// Mock the console.warn function
|
|
189
|
+
console.warn = jest.fn();
|
|
190
|
+
|
|
191
|
+
// Mock the console.error function
|
|
192
|
+
console.error = jest.fn();
|
|
193
|
+
|
|
194
|
+
describe("formatter", () => {
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
jest.clearAllMocks();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
it("should sort the JSON properties according to the predefined order", () => {
|
|
200
|
+
const filename = "valid.json";
|
|
201
|
+
formatter(filename);
|
|
202
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
203
|
+
`\u001B[32mSuccessfully sorted and written back to ${filename}\u001B[0m`
|
|
204
|
+
);
|
|
205
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
206
|
+
"valid.json",
|
|
207
|
+
JSON.stringify(expectedOutput1, null, 2),
|
|
208
|
+
"utf8"
|
|
209
|
+
);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("should handle undefined keys by appending them at the end in alphabetical order", () => {
|
|
213
|
+
formatter("undefined_keys.json");
|
|
214
|
+
expect(console.warn).toHaveBeenCalledWith(
|
|
215
|
+
"\u001B[33mWarning: Undefined key 'undefinedKey1' found in JSON\u001B[0m"
|
|
216
|
+
);
|
|
217
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
218
|
+
"\u001B[32mSuccessfully sorted and written back to undefined_keys.json\u001B[0m"
|
|
219
|
+
);
|
|
220
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
221
|
+
"undefined_keys.json",
|
|
222
|
+
JSON.stringify(expectedOutput2, null, 2),
|
|
223
|
+
"utf8"
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should handle empty JSON input", () => {
|
|
228
|
+
formatter("empty.json");
|
|
229
|
+
expect(console.log).toHaveBeenCalledWith(
|
|
230
|
+
"\u001B[32mSuccessfully sorted and written back to empty.json\u001B[0m"
|
|
231
|
+
);
|
|
232
|
+
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
|
233
|
+
"empty.json",
|
|
234
|
+
JSON.stringify(expectedOutput3, null, 2),
|
|
235
|
+
"utf8"
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it("should handle invalid JSON input", () => {
|
|
240
|
+
formatter("invalid.json");
|
|
241
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
242
|
+
"\u001B[31mError parsing JSON: SyntaxError: Unexpected token 'i', \"invalid json\" is not valid JSON\u001B[0m"
|
|
243
|
+
);
|
|
244
|
+
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("should handle file read error", () => {
|
|
248
|
+
formatter("nonexistent.json");
|
|
249
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
250
|
+
"\u001B[31mError: File not found: nonexistent.json\u001B[0m"
|
|
251
|
+
);
|
|
252
|
+
expect(fs.writeFileSync).not.toHaveBeenCalled();
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it("should handle file write error", () => {
|
|
256
|
+
fs.writeFileSync.mockImplementationOnce((filename, data, encoding) => {
|
|
257
|
+
throw new Error("Write error");
|
|
258
|
+
});
|
|
259
|
+
formatter("valid.json");
|
|
260
|
+
expect(console.error).toHaveBeenCalledWith(
|
|
261
|
+
"\u001B[31mError writing file: Error: Write error\u001B[0m"
|
|
262
|
+
);
|
|
263
|
+
});
|
|
264
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fixdevcontainer",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "fixdevcontainer.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"fixdevcontainer": "fixdevcontainer.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [],
|
|
10
|
+
"author": "",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"jest": "29.7.0"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "jest"
|
|
17
|
+
}
|
|
18
|
+
}
|
package/renovate.json
ADDED