clispark 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/cli.js +180 -115
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/dotnet/ARCHITECTURE.md +21 -0
- package/templates/dotnet/README.md +5 -0
- package/templates/node/ARCHITECTURE.md +22 -0
- package/templates/node/README.md +6 -0
- package/templates/node/package.json +3 -1
- package/templates/powershell/ARCHITECTURE.md +8 -0
- package/templates/powershell/Public/Complete-Task.ps1 +10 -0
- package/templates/powershell/Public/Get-Task.ps1 +17 -0
- package/templates/powershell/Public/New-Task.ps1 +17 -0
- package/templates/powershell/README.md +7 -0
- package/templates/powershell/tests/Complete-Task.Tests.ps1 +5 -0
- package/templates/powershell/tests/Get-Task.Tests.ps1 +13 -0
- package/templates/powershell/tests/New-Task.Tests.ps1 +9 -0
package/dist/cli.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import path22 from "path";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// src/wizard.ts
|
|
8
8
|
import { intro, outro, text as text2, select as select2, log, isCancel as isCancel2, cancel as cancel2 } from "@clack/prompts";
|
|
9
9
|
|
|
10
10
|
// src/languages/packs/node-oclif.ts
|
|
11
|
-
import
|
|
11
|
+
import path7 from "path";
|
|
12
12
|
|
|
13
13
|
// src/package-root.ts
|
|
14
14
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -31,8 +31,8 @@ function findPackageRoot() {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// src/update/adapters/node-oclif.ts
|
|
34
|
-
import { readFile as
|
|
35
|
-
import
|
|
34
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
35
|
+
import path4 from "path";
|
|
36
36
|
|
|
37
37
|
// src/update/reconcile.ts
|
|
38
38
|
function reconcileEntry(currentLiveValue, oldManifestValue, newTemplateValue, isEqual) {
|
|
@@ -73,6 +73,24 @@ async function stripLintTooling(targetDir) {
|
|
|
73
73
|
await writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// src/languages/autocomplete-support/node.ts
|
|
77
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
78
|
+
import path3 from "path";
|
|
79
|
+
var AUTOCOMPLETE_DEPENDENCY_NAME = "@oclif/plugin-autocomplete";
|
|
80
|
+
function withoutAutocompletePlugin(oclif) {
|
|
81
|
+
if (!oclif || typeof oclif !== "object") return oclif;
|
|
82
|
+
const shape = oclif;
|
|
83
|
+
if (!Array.isArray(shape.plugins)) return oclif;
|
|
84
|
+
return { ...shape, plugins: shape.plugins.filter((name) => name !== AUTOCOMPLETE_DEPENDENCY_NAME) };
|
|
85
|
+
}
|
|
86
|
+
async function stripAutocompleteSupport(targetDir) {
|
|
87
|
+
const pkgPath = path3.join(targetDir, "package.json");
|
|
88
|
+
const pkg = JSON.parse(await readFile2(pkgPath, "utf8"));
|
|
89
|
+
delete pkg.dependencies?.[AUTOCOMPLETE_DEPENDENCY_NAME];
|
|
90
|
+
pkg.oclif = withoutAutocompletePlugin(pkg.oclif);
|
|
91
|
+
await writeFile2(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
92
|
+
}
|
|
93
|
+
|
|
76
94
|
// src/update/adapters/node-oclif.ts
|
|
77
95
|
var CORE_FILE_PATHS = [
|
|
78
96
|
"bin/run.ts",
|
|
@@ -117,7 +135,7 @@ function mergePackageJson(currentPkg, oldManifest, newTemplatePkg) {
|
|
|
117
135
|
const coreDependencies = {};
|
|
118
136
|
const dependencyNames = new Set(
|
|
119
137
|
[...Object.keys(newTemplatePkg.dependencies ?? {}), ...Object.keys(newTemplatePkg.devDependencies ?? {})].filter(
|
|
120
|
-
(name) => oldManifest.lintEnabled || !LINT_DEPENDENCY_NAMES.includes(name)
|
|
138
|
+
(name) => (oldManifest.lintEnabled || !LINT_DEPENDENCY_NAMES.includes(name)) && (oldManifest.autocompleteEnabled || name !== AUTOCOMPLETE_DEPENDENCY_NAME)
|
|
121
139
|
)
|
|
122
140
|
);
|
|
123
141
|
for (const name of dependencyNames) {
|
|
@@ -169,7 +187,8 @@ function mergePackageJson(currentPkg, oldManifest, newTemplatePkg) {
|
|
|
169
187
|
}
|
|
170
188
|
}
|
|
171
189
|
if (newTemplatePkg.oclif !== void 0) {
|
|
172
|
-
const
|
|
190
|
+
const effectiveTemplateOclif = oldManifest.autocompleteEnabled ? newTemplatePkg.oclif : withoutAutocompletePlugin(newTemplatePkg.oclif);
|
|
191
|
+
const oclifResult = reconcileEntry(currentPkg.oclif, oldCoreFields.oclif, effectiveTemplateOclif, deepEquals);
|
|
173
192
|
fields.push({ key: "oclif", outcome: oclifResult.outcome });
|
|
174
193
|
oclifValue = oclifResult.value;
|
|
175
194
|
if (oclifResult.outcome !== "skipped" && !deepEquals(oclifResult.value, currentPkg.oclif)) {
|
|
@@ -197,11 +216,11 @@ var nodeOclifAdapter = {
|
|
|
197
216
|
},
|
|
198
217
|
manifestFileName: "package.json",
|
|
199
218
|
async readManifestFile(dir) {
|
|
200
|
-
const content = await
|
|
219
|
+
const content = await readFile3(path4.join(dir, "package.json"), "utf8");
|
|
201
220
|
return JSON.parse(content);
|
|
202
221
|
},
|
|
203
222
|
async writeManifestFile(dir, content) {
|
|
204
|
-
await
|
|
223
|
+
await writeFile3(path4.join(dir, "package.json"), JSON.stringify(content, null, 2) + "\n");
|
|
205
224
|
},
|
|
206
225
|
parseManifestFile(rawContent) {
|
|
207
226
|
return JSON.parse(rawContent);
|
|
@@ -218,8 +237,8 @@ var nodeOclifAdapter = {
|
|
|
218
237
|
};
|
|
219
238
|
|
|
220
239
|
// src/languages/registry-checkers/npm.ts
|
|
221
|
-
import { readFile as
|
|
222
|
-
import
|
|
240
|
+
import { readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
|
|
241
|
+
import path5 from "path";
|
|
223
242
|
var NPM_DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
224
243
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
225
244
|
async function checkNameAvailability(name, registryUrl) {
|
|
@@ -234,13 +253,13 @@ async function checkNameAvailability(name, registryUrl) {
|
|
|
234
253
|
}
|
|
235
254
|
}
|
|
236
255
|
async function applyPrivateIntent(targetDir) {
|
|
237
|
-
const packageJsonPath =
|
|
238
|
-
const pkg = JSON.parse(await
|
|
256
|
+
const packageJsonPath = path5.join(targetDir, "package.json");
|
|
257
|
+
const pkg = JSON.parse(await readFile4(packageJsonPath, "utf8"));
|
|
239
258
|
pkg.private = true;
|
|
240
|
-
await
|
|
259
|
+
await writeFile4(packageJsonPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
241
260
|
}
|
|
242
261
|
async function applyRegistryUrl(targetDir, registryUrl) {
|
|
243
|
-
await
|
|
262
|
+
await writeFile4(path5.join(targetDir, ".npmrc"), `registry=${registryUrl}
|
|
244
263
|
`);
|
|
245
264
|
}
|
|
246
265
|
var npmRegistryChecker = {
|
|
@@ -250,8 +269,8 @@ var npmRegistryChecker = {
|
|
|
250
269
|
};
|
|
251
270
|
|
|
252
271
|
// src/languages/command-generators/node-oclif.ts
|
|
253
|
-
import { mkdir, readdir, writeFile as
|
|
254
|
-
import
|
|
272
|
+
import { mkdir, readdir, writeFile as writeFile5 } from "fs/promises";
|
|
273
|
+
import path6 from "path";
|
|
255
274
|
|
|
256
275
|
// src/languages/command-generator.ts
|
|
257
276
|
function buildCommandTree(paths2) {
|
|
@@ -283,20 +302,20 @@ async function collectCommandFiles(dir, baseDir = dir) {
|
|
|
283
302
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
284
303
|
const files = [];
|
|
285
304
|
for (const entry of entries) {
|
|
286
|
-
const fullPath =
|
|
305
|
+
const fullPath = path6.join(dir, entry.name);
|
|
287
306
|
if (entry.isDirectory()) {
|
|
288
307
|
files.push(...await collectCommandFiles(fullPath, baseDir));
|
|
289
308
|
} else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".test.ts")) {
|
|
290
|
-
files.push(
|
|
309
|
+
files.push(path6.relative(baseDir, fullPath));
|
|
291
310
|
}
|
|
292
311
|
}
|
|
293
312
|
return files;
|
|
294
313
|
}
|
|
295
314
|
function toCommandPath(relativeFilePath) {
|
|
296
|
-
return relativeFilePath.replace(/\.ts$/, "").split(
|
|
315
|
+
return relativeFilePath.replace(/\.ts$/, "").split(path6.sep).join(" ");
|
|
297
316
|
}
|
|
298
317
|
async function listExistingCommands(targetDir) {
|
|
299
|
-
const commandsDir =
|
|
318
|
+
const commandsDir = path6.join(targetDir, "src", "commands");
|
|
300
319
|
const files = await collectCommandFiles(commandsDir);
|
|
301
320
|
return buildCommandTree(files.map(toCommandPath));
|
|
302
321
|
}
|
|
@@ -356,13 +375,13 @@ describe('${commandInvocation}', () => {
|
|
|
356
375
|
`;
|
|
357
376
|
}
|
|
358
377
|
async function generateCommand(targetDir, spec) {
|
|
359
|
-
const relDir =
|
|
378
|
+
const relDir = path6.join("src", "commands", ...spec.pathSegments.slice(0, -1));
|
|
360
379
|
const fileName = spec.pathSegments[spec.pathSegments.length - 1];
|
|
361
|
-
const commandRelPath =
|
|
362
|
-
const testRelPath =
|
|
363
|
-
await mkdir(
|
|
364
|
-
await
|
|
365
|
-
await
|
|
380
|
+
const commandRelPath = path6.join(relDir, `${fileName}.ts`);
|
|
381
|
+
const testRelPath = path6.join(relDir, `${fileName}.test.ts`);
|
|
382
|
+
await mkdir(path6.join(targetDir, relDir), { recursive: true });
|
|
383
|
+
await writeFile5(path6.join(targetDir, commandRelPath), generateCommandFileContent(spec));
|
|
384
|
+
await writeFile5(path6.join(targetDir, testRelPath), generateTestFileContent(spec));
|
|
366
385
|
return { commandFile: commandRelPath, testFile: testRelPath };
|
|
367
386
|
}
|
|
368
387
|
var nodeOclifCommandGenerator = {
|
|
@@ -381,7 +400,7 @@ function validateProjectName(value) {
|
|
|
381
400
|
var nodeOclifPack = {
|
|
382
401
|
id: "node",
|
|
383
402
|
displayName: "Node.js / TypeScript (oclif)",
|
|
384
|
-
templateDir:
|
|
403
|
+
templateDir: path7.join(findPackageRoot(), "templates", "node"),
|
|
385
404
|
scaffoldCommands: [
|
|
386
405
|
{ command: "npm", args: ["install"] },
|
|
387
406
|
{ command: "npm", args: ["run", "build"] }
|
|
@@ -396,15 +415,17 @@ var nodeOclifPack = {
|
|
|
396
415
|
applyRegistryUrl: npmRegistryChecker.applyRegistryUrl
|
|
397
416
|
},
|
|
398
417
|
commandGenerator: nodeOclifCommandGenerator,
|
|
399
|
-
stripLintTooling
|
|
418
|
+
stripLintTooling,
|
|
419
|
+
supportsAutocompleteOptIn: true,
|
|
420
|
+
stripAutocompleteSupport
|
|
400
421
|
};
|
|
401
422
|
|
|
402
423
|
// src/languages/packs/dotnet.ts
|
|
403
|
-
import
|
|
424
|
+
import path12 from "path";
|
|
404
425
|
|
|
405
426
|
// src/update/adapters/dotnet.ts
|
|
406
|
-
import { readFile as
|
|
407
|
-
import
|
|
427
|
+
import { readFile as readFile5, writeFile as writeFile6 } from "fs/promises";
|
|
428
|
+
import path8 from "path";
|
|
408
429
|
var CORE_FILE_PATHS2 = [
|
|
409
430
|
"Cli.slnx",
|
|
410
431
|
"src/Program.cs",
|
|
@@ -564,11 +585,11 @@ var dotnetAdapter = {
|
|
|
564
585
|
},
|
|
565
586
|
manifestFileName: "src/Cli.csproj",
|
|
566
587
|
async readManifestFile(dir) {
|
|
567
|
-
const content = await
|
|
588
|
+
const content = await readFile5(path8.join(dir, "src", "Cli.csproj"), "utf8");
|
|
568
589
|
return parseManifestFile(content);
|
|
569
590
|
},
|
|
570
591
|
async writeManifestFile(dir, content) {
|
|
571
|
-
await
|
|
592
|
+
await writeFile6(path8.join(dir, "src", "Cli.csproj"), content.raw);
|
|
572
593
|
},
|
|
573
594
|
parseManifestFile,
|
|
574
595
|
readProjectName(manifestFile) {
|
|
@@ -583,8 +604,8 @@ var dotnetAdapter = {
|
|
|
583
604
|
};
|
|
584
605
|
|
|
585
606
|
// src/languages/registry-checkers/nuget.ts
|
|
586
|
-
import { mkdir as mkdir2, readFile as
|
|
587
|
-
import
|
|
607
|
+
import { mkdir as mkdir2, readFile as readFile6, writeFile as writeFile7 } from "fs/promises";
|
|
608
|
+
import path9 from "path";
|
|
588
609
|
var NUGET_DEFAULT_REGISTRY_URL = "https://api.nuget.org/v3/index.json";
|
|
589
610
|
var NUGET_FLATCONTAINER_BASE = "https://api.nuget.org/v3-flatcontainer";
|
|
590
611
|
var FETCH_TIMEOUT_MS2 = 5e3;
|
|
@@ -600,13 +621,13 @@ async function checkNameAvailability2(name) {
|
|
|
600
621
|
}
|
|
601
622
|
}
|
|
602
623
|
async function applyPrivateIntent2(targetDir) {
|
|
603
|
-
const csprojPath =
|
|
604
|
-
const content = await
|
|
624
|
+
const csprojPath = path9.join(targetDir, "src", "Cli.csproj");
|
|
625
|
+
const content = await readFile6(csprojPath, "utf8");
|
|
605
626
|
const updated = content.replace(
|
|
606
627
|
/(<PropertyGroup>\s*\n)/,
|
|
607
628
|
"$1 <IsPackable>false</IsPackable>\n"
|
|
608
629
|
);
|
|
609
|
-
await
|
|
630
|
+
await writeFile7(csprojPath, updated);
|
|
610
631
|
}
|
|
611
632
|
async function applyRegistryUrl2(targetDir, registryUrl) {
|
|
612
633
|
const config = [
|
|
@@ -620,7 +641,7 @@ async function applyRegistryUrl2(targetDir, registryUrl) {
|
|
|
620
641
|
""
|
|
621
642
|
].join("\n");
|
|
622
643
|
await mkdir2(targetDir, { recursive: true });
|
|
623
|
-
await
|
|
644
|
+
await writeFile7(path9.join(targetDir, "NuGet.config"), config);
|
|
624
645
|
}
|
|
625
646
|
var nugetRegistryChecker = {
|
|
626
647
|
checkNameAvailability: checkNameAvailability2,
|
|
@@ -629,15 +650,15 @@ var nugetRegistryChecker = {
|
|
|
629
650
|
};
|
|
630
651
|
|
|
631
652
|
// src/languages/command-generators/dotnet.ts
|
|
632
|
-
import { mkdir as mkdir3, readFile as
|
|
633
|
-
import
|
|
653
|
+
import { mkdir as mkdir3, readFile as readFile7, readdir as readdir2, writeFile as writeFile8 } from "fs/promises";
|
|
654
|
+
import path10 from "path";
|
|
634
655
|
async function listExistingCommands2(targetDir) {
|
|
635
|
-
const commandsDir =
|
|
656
|
+
const commandsDir = path10.join(targetDir, "src", "Commands");
|
|
636
657
|
const files = await readdir2(commandsDir);
|
|
637
658
|
const paths2 = [];
|
|
638
659
|
for (const file of files) {
|
|
639
660
|
if (!file.endsWith(".cs")) continue;
|
|
640
|
-
const content = await
|
|
661
|
+
const content = await readFile7(path10.join(commandsDir, file), "utf8");
|
|
641
662
|
const match = content.match(/\[CommandPath\("([^"]+)"\)\]/);
|
|
642
663
|
if (match) paths2.push(match[1]);
|
|
643
664
|
}
|
|
@@ -729,12 +750,12 @@ public class ${className}Tests
|
|
|
729
750
|
}
|
|
730
751
|
async function generateCommand2(targetDir, spec) {
|
|
731
752
|
const className = toClassName2(spec.pathSegments);
|
|
732
|
-
const commandRelPath =
|
|
733
|
-
const testRelPath =
|
|
734
|
-
await mkdir3(
|
|
735
|
-
await mkdir3(
|
|
736
|
-
await
|
|
737
|
-
await
|
|
753
|
+
const commandRelPath = path10.join("src", "Commands", `${className}.cs`);
|
|
754
|
+
const testRelPath = path10.join("tests", `${className}Tests.cs`);
|
|
755
|
+
await mkdir3(path10.join(targetDir, "src", "Commands"), { recursive: true });
|
|
756
|
+
await mkdir3(path10.join(targetDir, "tests"), { recursive: true });
|
|
757
|
+
await writeFile8(path10.join(targetDir, commandRelPath), generateCommandFileContent2(spec));
|
|
758
|
+
await writeFile8(path10.join(targetDir, testRelPath), generateTestFileContent2(spec));
|
|
738
759
|
return { commandFile: commandRelPath, testFile: testRelPath };
|
|
739
760
|
}
|
|
740
761
|
var dotnetCommandGenerator = {
|
|
@@ -743,16 +764,16 @@ var dotnetCommandGenerator = {
|
|
|
743
764
|
};
|
|
744
765
|
|
|
745
766
|
// src/languages/lint-support/dotnet.ts
|
|
746
|
-
import { readFile as
|
|
747
|
-
import
|
|
767
|
+
import { readFile as readFile8, writeFile as writeFile9 } from "fs/promises";
|
|
768
|
+
import path11 from "path";
|
|
748
769
|
async function stripLintTooling2(targetDir) {
|
|
749
|
-
const csprojPath =
|
|
750
|
-
const content = await
|
|
770
|
+
const csprojPath = path11.join(targetDir, "src", "Cli.csproj");
|
|
771
|
+
const content = await readFile8(csprojPath, "utf8");
|
|
751
772
|
const updated = content.replace(
|
|
752
773
|
/\r?\n\s*<PropertyGroup>\s*\r?\n\s*<EnableNETAnalyzers>[\s\S]*?<\/PropertyGroup>\r?\n/,
|
|
753
774
|
"\n"
|
|
754
775
|
);
|
|
755
|
-
await
|
|
776
|
+
await writeFile9(csprojPath, updated);
|
|
756
777
|
}
|
|
757
778
|
|
|
758
779
|
// src/languages/packs/dotnet.ts
|
|
@@ -766,7 +787,7 @@ function validateProjectName2(value) {
|
|
|
766
787
|
var dotnetPack = {
|
|
767
788
|
id: "dotnet",
|
|
768
789
|
displayName: ".NET / C# (System.CommandLine)",
|
|
769
|
-
templateDir:
|
|
790
|
+
templateDir: path12.join(findPackageRoot(), "templates", "dotnet"),
|
|
770
791
|
scaffoldCommands: [
|
|
771
792
|
{ command: "dotnet", args: ["restore"] },
|
|
772
793
|
{ command: "dotnet", args: ["build"] }
|
|
@@ -781,15 +802,18 @@ var dotnetPack = {
|
|
|
781
802
|
applyRegistryUrl: nugetRegistryChecker.applyRegistryUrl
|
|
782
803
|
},
|
|
783
804
|
commandGenerator: dotnetCommandGenerator,
|
|
784
|
-
stripLintTooling: stripLintTooling2
|
|
805
|
+
stripLintTooling: stripLintTooling2,
|
|
806
|
+
supportsAutocompleteOptIn: false,
|
|
807
|
+
stripAutocompleteSupport: async () => {
|
|
808
|
+
}
|
|
785
809
|
};
|
|
786
810
|
|
|
787
811
|
// src/languages/packs/powershell.ts
|
|
788
|
-
import
|
|
812
|
+
import path16 from "path";
|
|
789
813
|
|
|
790
814
|
// src/update/adapters/powershell.ts
|
|
791
|
-
import { readFile as
|
|
792
|
-
import
|
|
815
|
+
import { readFile as readFile9, writeFile as writeFile10 } from "fs/promises";
|
|
816
|
+
import path13 from "path";
|
|
793
817
|
import spawn from "cross-spawn";
|
|
794
818
|
var CORE_FILE_PATHS3 = [
|
|
795
819
|
"Module.psm1",
|
|
@@ -910,8 +934,8 @@ var powershellAdapter = {
|
|
|
910
934
|
},
|
|
911
935
|
manifestFileName: "Module.psd1",
|
|
912
936
|
async readManifestFile(dir) {
|
|
913
|
-
const manifestPath =
|
|
914
|
-
const raw = await
|
|
937
|
+
const manifestPath = path13.join(dir, "Module.psd1");
|
|
938
|
+
const raw = await readFile9(manifestPath, "utf8");
|
|
915
939
|
const parsedViaPwsh = await readManifestViaPwsh(manifestPath);
|
|
916
940
|
return {
|
|
917
941
|
raw,
|
|
@@ -920,7 +944,7 @@ var powershellAdapter = {
|
|
|
920
944
|
};
|
|
921
945
|
},
|
|
922
946
|
async writeManifestFile(dir, content) {
|
|
923
|
-
await
|
|
947
|
+
await writeFile10(path13.join(dir, "Module.psd1"), content.raw);
|
|
924
948
|
},
|
|
925
949
|
parseManifestFile: parseManifestFile2,
|
|
926
950
|
readProjectName() {
|
|
@@ -935,8 +959,8 @@ var powershellAdapter = {
|
|
|
935
959
|
};
|
|
936
960
|
|
|
937
961
|
// src/languages/registry-checkers/powershell-gallery.ts
|
|
938
|
-
import { mkdir as mkdir4, writeFile as
|
|
939
|
-
import
|
|
962
|
+
import { mkdir as mkdir4, writeFile as writeFile11 } from "fs/promises";
|
|
963
|
+
import path14 from "path";
|
|
940
964
|
var POWERSHELL_GALLERY_DEFAULT_URL = "https://www.powershellgallery.com/api/v2";
|
|
941
965
|
var FETCH_TIMEOUT_MS3 = 5e3;
|
|
942
966
|
async function checkNameAvailability3(name, registryUrl) {
|
|
@@ -962,7 +986,7 @@ async function applyRegistryUrl3(targetDir, registryUrl) {
|
|
|
962
986
|
""
|
|
963
987
|
].join("\n");
|
|
964
988
|
await mkdir4(targetDir, { recursive: true });
|
|
965
|
-
await
|
|
989
|
+
await writeFile11(path14.join(targetDir, ".psresource-repository"), content);
|
|
966
990
|
}
|
|
967
991
|
var powershellGalleryRegistryChecker = {
|
|
968
992
|
checkNameAvailability: checkNameAvailability3,
|
|
@@ -971,8 +995,8 @@ var powershellGalleryRegistryChecker = {
|
|
|
971
995
|
};
|
|
972
996
|
|
|
973
997
|
// src/languages/command-generators/powershell.ts
|
|
974
|
-
import { mkdir as mkdir5, readdir as readdir3, writeFile as
|
|
975
|
-
import
|
|
998
|
+
import { mkdir as mkdir5, readdir as readdir3, writeFile as writeFile12 } from "fs/promises";
|
|
999
|
+
import path15 from "path";
|
|
976
1000
|
import { select, text, isCancel, cancel } from "@clack/prompts";
|
|
977
1001
|
var APPROVED_VERBS = [
|
|
978
1002
|
"Get",
|
|
@@ -996,7 +1020,7 @@ function exitIfCancelled(value) {
|
|
|
996
1020
|
}
|
|
997
1021
|
}
|
|
998
1022
|
async function listExistingCommands3(targetDir) {
|
|
999
|
-
const publicDir =
|
|
1023
|
+
const publicDir = path15.join(targetDir, "Public");
|
|
1000
1024
|
const files = await readdir3(publicDir);
|
|
1001
1025
|
const paths2 = files.filter((f) => f.endsWith(".ps1")).map((f) => f.replace(/\.ps1$/, ""));
|
|
1002
1026
|
return buildCommandTree(paths2);
|
|
@@ -1051,12 +1075,12 @@ function generateTestFileContent3(spec) {
|
|
|
1051
1075
|
}
|
|
1052
1076
|
async function generateCommand3(targetDir, spec) {
|
|
1053
1077
|
const funcName = spec.pathSegments[spec.pathSegments.length - 1];
|
|
1054
|
-
const commandRelPath =
|
|
1055
|
-
const testRelPath =
|
|
1056
|
-
await mkdir5(
|
|
1057
|
-
await mkdir5(
|
|
1058
|
-
await
|
|
1059
|
-
await
|
|
1078
|
+
const commandRelPath = path15.join("Public", `${funcName}.ps1`);
|
|
1079
|
+
const testRelPath = path15.join("tests", `${funcName}.Tests.ps1`);
|
|
1080
|
+
await mkdir5(path15.join(targetDir, "Public"), { recursive: true });
|
|
1081
|
+
await mkdir5(path15.join(targetDir, "tests"), { recursive: true });
|
|
1082
|
+
await writeFile12(path15.join(targetDir, commandRelPath), generateCommandFileContent3(spec));
|
|
1083
|
+
await writeFile12(path15.join(targetDir, testRelPath), generateTestFileContent3(spec));
|
|
1060
1084
|
return { commandFile: commandRelPath.replace(/\\/g, "/"), testFile: testRelPath.replace(/\\/g, "/") };
|
|
1061
1085
|
}
|
|
1062
1086
|
async function promptCommandIdentity(_pathSegments, existingPaths) {
|
|
@@ -1094,7 +1118,7 @@ function validateProjectName3(value) {
|
|
|
1094
1118
|
var powershellPack = {
|
|
1095
1119
|
id: "powershell",
|
|
1096
1120
|
displayName: "PowerShell (7.4+)",
|
|
1097
|
-
templateDir:
|
|
1121
|
+
templateDir: path16.join(findPackageRoot(), "templates", "powershell"),
|
|
1098
1122
|
scaffoldCommands: [
|
|
1099
1123
|
{
|
|
1100
1124
|
command: "pwsh",
|
|
@@ -1112,6 +1136,9 @@ var powershellPack = {
|
|
|
1112
1136
|
},
|
|
1113
1137
|
commandGenerator: powershellCommandGenerator,
|
|
1114
1138
|
stripLintTooling: async () => {
|
|
1139
|
+
},
|
|
1140
|
+
supportsAutocompleteOptIn: false,
|
|
1141
|
+
stripAutocompleteSupport: async () => {
|
|
1115
1142
|
}
|
|
1116
1143
|
};
|
|
1117
1144
|
|
|
@@ -1186,6 +1213,19 @@ async function runWizard(deps = defaultDeps) {
|
|
|
1186
1213
|
});
|
|
1187
1214
|
exitIfCancelled2(lintEnabledValue);
|
|
1188
1215
|
const lintEnabled = lintEnabledValue;
|
|
1216
|
+
let autocompleteEnabled = false;
|
|
1217
|
+
if (pack.supportsAutocompleteOptIn) {
|
|
1218
|
+
const autocompleteEnabledValue = await select2({
|
|
1219
|
+
message: "Set up shell autocompletion?",
|
|
1220
|
+
options: [
|
|
1221
|
+
{ value: false, label: "No" },
|
|
1222
|
+
{ value: true, label: "Yes" }
|
|
1223
|
+
],
|
|
1224
|
+
initialValue: false
|
|
1225
|
+
});
|
|
1226
|
+
exitIfCancelled2(autocompleteEnabledValue);
|
|
1227
|
+
autocompleteEnabled = autocompleteEnabledValue;
|
|
1228
|
+
}
|
|
1189
1229
|
let nameAvailability = "skipped";
|
|
1190
1230
|
if (publishIntent) {
|
|
1191
1231
|
nameAvailability = await pack.registry.checkNameAvailability(projectName, registryUrl);
|
|
@@ -1204,19 +1244,28 @@ async function runWizard(deps = defaultDeps) {
|
|
|
1204
1244
|
}
|
|
1205
1245
|
}
|
|
1206
1246
|
outro(`Ready to scaffold "${projectName}".`);
|
|
1207
|
-
return {
|
|
1247
|
+
return {
|
|
1248
|
+
language: pack.id,
|
|
1249
|
+
projectName,
|
|
1250
|
+
profile,
|
|
1251
|
+
registryUrl,
|
|
1252
|
+
publishIntent,
|
|
1253
|
+
nameAvailability,
|
|
1254
|
+
lintEnabled,
|
|
1255
|
+
autocompleteEnabled
|
|
1256
|
+
};
|
|
1208
1257
|
}
|
|
1209
1258
|
|
|
1210
1259
|
// src/scaffold.ts
|
|
1211
|
-
import { cp, readdir as readdir4, readFile as
|
|
1212
|
-
import
|
|
1260
|
+
import { cp, readdir as readdir4, readFile as readFile11, rename, writeFile as writeFile14 } from "fs/promises";
|
|
1261
|
+
import path18 from "path";
|
|
1213
1262
|
import spawn2 from "cross-spawn";
|
|
1214
1263
|
|
|
1215
1264
|
// src/update/manifest.ts
|
|
1216
1265
|
import { createHash } from "crypto";
|
|
1217
1266
|
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
1218
|
-
import { mkdir as mkdir6, readFile as
|
|
1219
|
-
import
|
|
1267
|
+
import { mkdir as mkdir6, readFile as readFile10, writeFile as writeFile13 } from "fs/promises";
|
|
1268
|
+
import path17 from "path";
|
|
1220
1269
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1221
1270
|
|
|
1222
1271
|
// src/errors.ts
|
|
@@ -1234,27 +1283,30 @@ function hashContent(content) {
|
|
|
1234
1283
|
async function hashCoreFiles(dir, adapter, flags) {
|
|
1235
1284
|
const entries = await Promise.all(
|
|
1236
1285
|
adapter.coreFilePaths(flags).map(async (relativePath) => {
|
|
1237
|
-
const content = await
|
|
1286
|
+
const content = await readFile10(path17.join(dir, relativePath), "utf8");
|
|
1238
1287
|
return [relativePath, hashContent(content)];
|
|
1239
1288
|
})
|
|
1240
1289
|
);
|
|
1241
1290
|
return Object.fromEntries(entries);
|
|
1242
1291
|
}
|
|
1243
|
-
async function buildManifest(targetDir, generatorVersion, language, adapter, lintEnabled) {
|
|
1244
|
-
const coreFiles = await hashCoreFiles(targetDir, adapter, { lintEnabled });
|
|
1292
|
+
async function buildManifest(targetDir, generatorVersion, language, adapter, lintEnabled, autocompleteEnabled) {
|
|
1293
|
+
const coreFiles = await hashCoreFiles(targetDir, adapter, { lintEnabled, autocompleteEnabled });
|
|
1245
1294
|
const manifestFile = await adapter.readManifestFile(targetDir);
|
|
1246
|
-
const { coreDependencies, coreScripts, coreFields } = adapter.extractCoreFields(manifestFile, {
|
|
1247
|
-
|
|
1295
|
+
const { coreDependencies, coreScripts, coreFields } = adapter.extractCoreFields(manifestFile, {
|
|
1296
|
+
lintEnabled,
|
|
1297
|
+
autocompleteEnabled
|
|
1298
|
+
});
|
|
1299
|
+
return { generatorVersion, language, lintEnabled, autocompleteEnabled, coreFiles, coreDependencies, coreScripts, coreFields };
|
|
1248
1300
|
}
|
|
1249
|
-
var MANIFEST_RELATIVE_PATH =
|
|
1301
|
+
var MANIFEST_RELATIVE_PATH = path17.join(".clispark", "manifest.json");
|
|
1250
1302
|
async function writeManifest(targetDir, manifest) {
|
|
1251
|
-
const manifestPath =
|
|
1252
|
-
await mkdir6(
|
|
1253
|
-
await
|
|
1303
|
+
const manifestPath = path17.join(targetDir, MANIFEST_RELATIVE_PATH);
|
|
1304
|
+
await mkdir6(path17.dirname(manifestPath), { recursive: true });
|
|
1305
|
+
await writeFile13(manifestPath, JSON.stringify(manifest, null, 2) + "\n");
|
|
1254
1306
|
}
|
|
1255
1307
|
async function readManifest(targetDir) {
|
|
1256
1308
|
try {
|
|
1257
|
-
const content = await
|
|
1309
|
+
const content = await readFile10(path17.join(targetDir, MANIFEST_RELATIVE_PATH), "utf8");
|
|
1258
1310
|
return JSON.parse(content);
|
|
1259
1311
|
} catch {
|
|
1260
1312
|
return void 0;
|
|
@@ -1270,14 +1322,14 @@ async function requireManifest(targetDir) {
|
|
|
1270
1322
|
return manifest;
|
|
1271
1323
|
}
|
|
1272
1324
|
function getGeneratorVersion() {
|
|
1273
|
-
let dir =
|
|
1325
|
+
let dir = path17.dirname(fileURLToPath2(import.meta.url));
|
|
1274
1326
|
while (true) {
|
|
1275
|
-
const pkgPath =
|
|
1327
|
+
const pkgPath = path17.join(dir, "package.json");
|
|
1276
1328
|
if (existsSync2(pkgPath)) {
|
|
1277
1329
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf8"));
|
|
1278
1330
|
if (pkg.name === "clispark") return pkg.version;
|
|
1279
1331
|
}
|
|
1280
|
-
const parentDir =
|
|
1332
|
+
const parentDir = path17.dirname(dir);
|
|
1281
1333
|
if (parentDir === dir) {
|
|
1282
1334
|
throw new Error("Could not locate clispark's own package.json.");
|
|
1283
1335
|
}
|
|
@@ -1304,7 +1356,7 @@ async function collectFiles(dir) {
|
|
|
1304
1356
|
const entries = await readdir4(dir, { withFileTypes: true });
|
|
1305
1357
|
const files = await Promise.all(
|
|
1306
1358
|
entries.map((entry) => {
|
|
1307
|
-
const fullPath =
|
|
1359
|
+
const fullPath = path18.join(dir, entry.name);
|
|
1308
1360
|
return entry.isDirectory() ? collectFiles(fullPath) : Promise.resolve([fullPath]);
|
|
1309
1361
|
})
|
|
1310
1362
|
);
|
|
@@ -1314,9 +1366,9 @@ async function replacePlaceholdersInTree(targetDir, projectName) {
|
|
|
1314
1366
|
const files = await collectFiles(targetDir);
|
|
1315
1367
|
await Promise.all(
|
|
1316
1368
|
files.map(async (filePath) => {
|
|
1317
|
-
const content = await
|
|
1369
|
+
const content = await readFile11(filePath, "utf8");
|
|
1318
1370
|
if (content.includes("{{projectName}}")) {
|
|
1319
|
-
await
|
|
1371
|
+
await writeFile14(filePath, applyPlaceholders(content, projectName));
|
|
1320
1372
|
}
|
|
1321
1373
|
})
|
|
1322
1374
|
);
|
|
@@ -1325,7 +1377,7 @@ async function copyTemplate(options, pack) {
|
|
|
1325
1377
|
const { projectName, targetDir, registryUrl, publishIntent } = options;
|
|
1326
1378
|
await assertTargetDirIsUsable(targetDir);
|
|
1327
1379
|
await cp(pack.templateDir, targetDir, { recursive: true });
|
|
1328
|
-
await rename(
|
|
1380
|
+
await rename(path18.join(targetDir, "gitignore"), path18.join(targetDir, ".gitignore"));
|
|
1329
1381
|
await replacePlaceholdersInTree(targetDir, projectName);
|
|
1330
1382
|
if (publishIntent === false) {
|
|
1331
1383
|
await pack.registry.applyPrivateIntent(targetDir);
|
|
@@ -1355,7 +1407,18 @@ async function scaffoldProject(options, pack, deps = defaultScaffoldDeps) {
|
|
|
1355
1407
|
if (!lintEnabled) {
|
|
1356
1408
|
await pack.stripLintTooling(targetDir);
|
|
1357
1409
|
}
|
|
1358
|
-
const
|
|
1410
|
+
const autocompleteEnabled = options.autocompleteEnabled ?? false;
|
|
1411
|
+
if (!autocompleteEnabled) {
|
|
1412
|
+
await pack.stripAutocompleteSupport(targetDir);
|
|
1413
|
+
}
|
|
1414
|
+
const manifest = await buildManifest(
|
|
1415
|
+
targetDir,
|
|
1416
|
+
getGeneratorVersion(),
|
|
1417
|
+
pack.id,
|
|
1418
|
+
pack.updateAdapter,
|
|
1419
|
+
lintEnabled,
|
|
1420
|
+
autocompleteEnabled
|
|
1421
|
+
);
|
|
1359
1422
|
await writeManifest(targetDir, manifest);
|
|
1360
1423
|
await deps.runCommand("git", ["init"], targetDir);
|
|
1361
1424
|
await deps.runCommand("git", ["add", "-A"], targetDir);
|
|
@@ -1368,7 +1431,7 @@ async function scaffoldProject(options, pack, deps = defaultScaffoldDeps) {
|
|
|
1368
1431
|
// src/logger.ts
|
|
1369
1432
|
import { randomBytes } from "crypto";
|
|
1370
1433
|
import { mkdirSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs";
|
|
1371
|
-
import
|
|
1434
|
+
import path19 from "path";
|
|
1372
1435
|
import envPaths from "env-paths";
|
|
1373
1436
|
import pino from "pino";
|
|
1374
1437
|
var paths = envPaths("clispark", { suffix: "" });
|
|
@@ -1404,7 +1467,7 @@ var SWEEP_MARKER_FILE = ".last-sweep";
|
|
|
1404
1467
|
var SWEEP_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
1405
1468
|
function sweepOldLogs(logDir) {
|
|
1406
1469
|
safely(() => {
|
|
1407
|
-
const markerPath =
|
|
1470
|
+
const markerPath = path19.join(logDir, SWEEP_MARKER_FILE);
|
|
1408
1471
|
let shouldSweep = true;
|
|
1409
1472
|
try {
|
|
1410
1473
|
shouldSweep = Date.now() - statSync(markerPath).mtimeMs >= SWEEP_THROTTLE_MS;
|
|
@@ -1414,7 +1477,7 @@ function sweepOldLogs(logDir) {
|
|
|
1414
1477
|
const cutoffMs = Date.now() - getRetentionDays() * 24 * 60 * 60 * 1e3;
|
|
1415
1478
|
for (const file of readdirSync(logDir)) {
|
|
1416
1479
|
if (file === SWEEP_MARKER_FILE) continue;
|
|
1417
|
-
const filePath =
|
|
1480
|
+
const filePath = path19.join(logDir, file);
|
|
1418
1481
|
if (statSync(filePath).mtimeMs < cutoffMs) {
|
|
1419
1482
|
unlinkSync(filePath);
|
|
1420
1483
|
}
|
|
@@ -1425,7 +1488,7 @@ function sweepOldLogs(logDir) {
|
|
|
1425
1488
|
function createLogger(commandName, logDir = paths.log) {
|
|
1426
1489
|
mkdirSync(logDir, { recursive: true });
|
|
1427
1490
|
sweepOldLogs(logDir);
|
|
1428
|
-
const logFilePath =
|
|
1491
|
+
const logFilePath = path19.join(logDir, buildLogFileName(commandName));
|
|
1429
1492
|
const fileDestination = pino.destination({ dest: logFilePath, sync: true, mode: 384 });
|
|
1430
1493
|
const destination = process.env.DEBUG ? pino.multistream([{ stream: fileDestination }, { stream: process.stdout }]) : fileDestination;
|
|
1431
1494
|
const logger = pino({ redact: buildRedactPaths([...SENSITIVE_LOG_KEYS, "registryUrl"]) }, destination);
|
|
@@ -1463,8 +1526,8 @@ function withLogging(commandName, action, logDir = paths.log, loggerFactory = cr
|
|
|
1463
1526
|
}
|
|
1464
1527
|
|
|
1465
1528
|
// src/update/update.ts
|
|
1466
|
-
import { mkdir as mkdir7, readFile as
|
|
1467
|
-
import
|
|
1529
|
+
import { mkdir as mkdir7, readFile as readFile12, writeFile as writeFile15 } from "fs/promises";
|
|
1530
|
+
import path20 from "path";
|
|
1468
1531
|
import spawn3 from "cross-spawn";
|
|
1469
1532
|
|
|
1470
1533
|
// src/update/releasenotes.ts
|
|
@@ -1550,7 +1613,7 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1550
1613
|
const currentManifestFile = await adapter.readManifestFile(targetDir);
|
|
1551
1614
|
const projectName = adapter.readProjectName(currentManifestFile);
|
|
1552
1615
|
const newTemplateRaw = applyPlaceholders(
|
|
1553
|
-
await
|
|
1616
|
+
await readFile12(path20.join(templateDir, adapter.manifestFileName), "utf8"),
|
|
1554
1617
|
projectName
|
|
1555
1618
|
);
|
|
1556
1619
|
const newTemplateManifestFile = adapter.parseManifestFile(newTemplateRaw);
|
|
@@ -1560,13 +1623,13 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1560
1623
|
const perFileResults = await Promise.all(
|
|
1561
1624
|
adapter.coreFilePaths(oldManifest).map(async (relativePath) => {
|
|
1562
1625
|
const newContent = applyPlaceholders(
|
|
1563
|
-
await
|
|
1626
|
+
await readFile12(path20.join(templateDir, adapter.templateSourcePath(relativePath)), "utf8"),
|
|
1564
1627
|
projectName
|
|
1565
1628
|
);
|
|
1566
1629
|
const newHash = hashContent(newContent);
|
|
1567
1630
|
let currentHash;
|
|
1568
1631
|
try {
|
|
1569
|
-
currentHash = hashContent(await
|
|
1632
|
+
currentHash = hashContent(await readFile12(path20.join(targetDir, relativePath), "utf8"));
|
|
1570
1633
|
} catch {
|
|
1571
1634
|
currentHash = void 0;
|
|
1572
1635
|
}
|
|
@@ -1578,13 +1641,13 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1578
1641
|
files.push({ path: relativePath, outcome: result.outcome });
|
|
1579
1642
|
newCoreFiles[relativePath] = result.value;
|
|
1580
1643
|
if (result.outcome === "added" || result.outcome === "replaced") {
|
|
1581
|
-
fileWrites.push({ targetPath:
|
|
1644
|
+
fileWrites.push({ targetPath: path20.join(targetDir, relativePath), content: newContent });
|
|
1582
1645
|
}
|
|
1583
1646
|
}
|
|
1584
1647
|
for (const relativePath of Object.keys(oldManifest.coreFiles)) {
|
|
1585
1648
|
if (adapter.coreFilePaths(oldManifest).includes(relativePath)) continue;
|
|
1586
1649
|
try {
|
|
1587
|
-
await
|
|
1650
|
+
await readFile12(path20.join(targetDir, relativePath), "utf8");
|
|
1588
1651
|
files.push({ path: relativePath, outcome: "no-longer-core" });
|
|
1589
1652
|
} catch {
|
|
1590
1653
|
}
|
|
@@ -1606,8 +1669,8 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1606
1669
|
};
|
|
1607
1670
|
}
|
|
1608
1671
|
for (const write of fileWrites) {
|
|
1609
|
-
await mkdir7(
|
|
1610
|
-
await
|
|
1672
|
+
await mkdir7(path20.dirname(write.targetPath), { recursive: true });
|
|
1673
|
+
await writeFile15(write.targetPath, write.content);
|
|
1611
1674
|
}
|
|
1612
1675
|
if (fileMerge.changed) {
|
|
1613
1676
|
await adapter.writeManifestFile(targetDir, fileMerge.updatedFile);
|
|
@@ -1616,6 +1679,7 @@ async function updateProject(targetDir, adapter, templateDir, language, deps = d
|
|
|
1616
1679
|
generatorVersion: toVersion,
|
|
1617
1680
|
language,
|
|
1618
1681
|
lintEnabled: oldManifest.lintEnabled ?? false,
|
|
1682
|
+
autocompleteEnabled: oldManifest.autocompleteEnabled ?? false,
|
|
1619
1683
|
coreFiles: newCoreFiles,
|
|
1620
1684
|
coreDependencies: fileMerge.coreDependencies,
|
|
1621
1685
|
coreScripts: fileMerge.coreScripts,
|
|
@@ -1925,11 +1989,11 @@ ${getConfetti(randomFn)}
|
|
|
1925
1989
|
|
|
1926
1990
|
// src/hooks.ts
|
|
1927
1991
|
import { existsSync as existsSync3 } from "fs";
|
|
1928
|
-
import
|
|
1992
|
+
import path21 from "path";
|
|
1929
1993
|
import { pathToFileURL } from "url";
|
|
1930
1994
|
import envPaths2 from "env-paths";
|
|
1931
1995
|
function getPostScaffoldHookPath(configDir = envPaths2("clispark", { suffix: "" }).config) {
|
|
1932
|
-
return
|
|
1996
|
+
return path21.join(configDir, "hooks", "post-scaffold.mjs");
|
|
1933
1997
|
}
|
|
1934
1998
|
var defaultDeps2 = {
|
|
1935
1999
|
fileExists: existsSync3,
|
|
@@ -1972,7 +2036,7 @@ function resolvePack(language) {
|
|
|
1972
2036
|
program.action(
|
|
1973
2037
|
(options) => withLogging("scaffold", async (logger) => {
|
|
1974
2038
|
const answers = await runWizard();
|
|
1975
|
-
const targetDir =
|
|
2039
|
+
const targetDir = path22.join(process.cwd(), answers.projectName);
|
|
1976
2040
|
const pack = resolvePack(answers.language);
|
|
1977
2041
|
logger.info({ projectName: answers.projectName, targetDir, language: pack.id }, "scaffold started");
|
|
1978
2042
|
await scaffoldProject(
|
|
@@ -1981,7 +2045,8 @@ program.action(
|
|
|
1981
2045
|
targetDir,
|
|
1982
2046
|
registryUrl: answers.registryUrl,
|
|
1983
2047
|
publishIntent: answers.publishIntent,
|
|
1984
|
-
lintEnabled: answers.lintEnabled
|
|
2048
|
+
lintEnabled: answers.lintEnabled,
|
|
2049
|
+
autocompleteEnabled: answers.autocompleteEnabled
|
|
1985
2050
|
},
|
|
1986
2051
|
pack
|
|
1987
2052
|
);
|