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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "ignores": ["jest"]
3
+ }
@@ -0,0 +1,15 @@
1
+ name: Auto add reviewer
2
+
3
+ on:
4
+ pull_request_target:
5
+ types:
6
+ - opened
7
+ - reopened
8
+ branches:
9
+ - main
10
+ - master
11
+
12
+ jobs:
13
+ add-reviewer:
14
+ name: Add reviewer
15
+ uses: book000/templates/.github/workflows/reusable-add-reviewer.yml@master
@@ -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
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": ["github>book000/templates//renovate/base-public"]
3
+ }