ic-mops 2.5.1 → 2.7.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/CHANGELOG.md +16 -0
- package/RELEASE.md +18 -15
- package/bundle/cli.tgz +0 -0
- package/cli.ts +1 -1
- package/commands/check.ts +20 -2
- package/commands/docs-coverage.ts +26 -21
- package/commands/lint.ts +107 -10
- package/commands/publish.ts +36 -18
- package/commands/self.ts +1 -1
- package/dist/cli.js +1 -1
- package/dist/commands/check.js +14 -2
- package/dist/commands/docs-coverage.js +22 -22
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +75 -10
- package/dist/commands/publish.js +26 -15
- package/dist/commands/self.js +1 -1
- package/dist/package.json +1 -1
- package/dist/release-cli.js +2 -2
- package/dist/tests/check.test.js +24 -0
- package/dist/tests/lint.test.js +28 -2
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
- package/release-cli.ts +2 -2
- package/tests/check/with-lint-fail/NoBoolSwitch.mo +8 -0
- package/tests/check/with-lint-fail/lints/no-bool-switch.toml +9 -0
- package/tests/check/with-lint-fail/mops.toml +9 -0
- package/tests/check/with-lint-pass/Ok.mo +5 -0
- package/tests/check/with-lint-pass/lints/no-bool-switch.toml +9 -0
- package/tests/check/with-lint-pass/mops.toml +9 -0
- package/tests/check.test.ts +28 -0
- package/tests/lint-config-rules/extra-rules/no-bool-switch.toml +9 -0
- package/tests/lint-config-rules/mops.toml +5 -0
- package/tests/lint-config-rules/src/NoBoolSwitch.mo +8 -0
- package/tests/lint-extends/mops.toml +8 -0
- package/tests/lint-extends/my-pkg/mops.toml +3 -0
- package/tests/lint-extends/my-pkg/rules/no-bool-switch.toml +9 -0
- package/tests/lint-extends/src/NoBoolSwitch.mo +8 -0
- package/tests/lint-extends-all/mops.toml +8 -0
- package/tests/lint-extends-all/src/NoBoolSwitch.mo +8 -0
- package/tests/lint-extends-ignored/mops.toml +8 -0
- package/tests/lint-extends-ignored/src/NoBoolSwitch.mo +8 -0
- package/tests/lint.test.ts +32 -2
- package/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## Next
|
|
4
4
|
|
|
5
|
+
## 2.7.0
|
|
6
|
+
|
|
7
|
+
- `mops publish` no longer requires a `repository` field — it is now optional metadata (used by the registry UI for source links)
|
|
8
|
+
- `mops publish` now hard-errors on GitHub `[dependencies]` instead of prompting; the backend has rejected them for some time and the prompt was misleading
|
|
9
|
+
- `mops publish` now fails fast with a clear error when unsupported fields (`dfx`, `moc`, `homepage`, `documentation`, `donation`) are set in `mops.toml`
|
|
10
|
+
- Fix `mops publish` reporting incorrect max length for `license` field (was 30, now matches backend limit of 40)
|
|
11
|
+
|
|
12
|
+
## 2.6.0
|
|
13
|
+
|
|
14
|
+
- Packages can ship lintoko rules for consumers in a `rules/` directory (distinct from `lint/`/`lints/` which check the package itself); `rules/*.toml` files are included automatically when running `mops publish`
|
|
15
|
+
- Add `[lint] extends` in `mops.toml` to pull in `rules/` from installed dependencies: `extends = ["pkg"]` for named packages or `extends = true` for all
|
|
16
|
+
- Add `[lint] rules` in `mops.toml` to override the default `lint/`/`lints/` rule directories with custom paths
|
|
17
|
+
- `mops check` now runs `mops lint` after a successful type-check when `lintoko` is pinned in `[toolchain]`; lint is scoped to explicitly passed files when given, otherwise covers all `.mo` files; `--fix` propagates to both steps
|
|
18
|
+
- Raise package file limit from 300 to 1000; `mops publish` now fails fast with a clear error if the limit is exceeded
|
|
19
|
+
- Fix `mops docs coverage` crashing with out-of-memory on packages with many source files (replaced JSDOM with a lightweight adoc parser)
|
|
20
|
+
|
|
5
21
|
## 2.5.1
|
|
6
22
|
- Fix `mops test` and `mops watch` breaking when dependency paths contain spaces
|
|
7
23
|
- Fix `mops sync` incorrectly reporting version-pinned dependencies as missing/unused
|
package/RELEASE.md
CHANGED
|
@@ -21,33 +21,36 @@ cd cli
|
|
|
21
21
|
npm version patch --no-git-tag-version # or: minor / major
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
## 3. Create a release PR
|
|
24
|
+
## 3. Create a release PR and enable auto-merge
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
git checkout -b <username>/release-X.Y.Z
|
|
28
28
|
git add cli/CHANGELOG.md cli/package.json cli/package-lock.json
|
|
29
29
|
git commit -m "release: CLI vX.Y.Z"
|
|
30
30
|
git push -u origin <username>/release-X.Y.Z
|
|
31
|
-
gh pr create
|
|
31
|
+
gh pr create \
|
|
32
|
+
--title "release: CLI vX.Y.Z" \
|
|
33
|
+
--body "Release CLI vX.Y.Z." \
|
|
34
|
+
--label release
|
|
35
|
+
gh pr merge --auto --squash
|
|
32
36
|
```
|
|
33
37
|
|
|
34
|
-
|
|
38
|
+
The [`release-pr.yml`](../.github/workflows/release-pr.yml) workflow runs on every update and validates:
|
|
39
|
+
- PR title matches `release: CLI vX.Y.Z`
|
|
40
|
+
- `cli/CHANGELOG.md` has an entry for the version
|
|
41
|
+
- `cli/package.json` version matches
|
|
35
42
|
|
|
36
|
-
|
|
43
|
+
Once all required checks pass the PR merges automatically. On merge, `release-pr.yml` pushes the `cli-vX.Y.Z` tag, which triggers the [`release.yml`](../.github/workflows/release.yml) workflow — it builds, publishes to npm, creates a GitHub Release, and deploys canisters (`cli.mops.one` and `docs.mops.one`).
|
|
37
44
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
This triggers the [`release.yml`](../.github/workflows/release.yml) workflow which builds, publishes to npm, creates a GitHub Release, deploys canisters (`cli.mops.one` and `docs.mops.one`), and opens a PR with on-chain release artifacts.
|
|
45
|
-
|
|
46
|
-
Monitor at [Actions → Release CLI](https://github.com/caffeinelabs/mops/actions/workflows/release.yml).
|
|
45
|
+
> **Note:** This workflow only deploys the `cli` and `docs` canisters. The `main`, `assets`, `blog`, and `play-frontend` canisters require a manual deploy. If a release includes changes to any of those (e.g. `backend/main/` or `frontend/`), upgrade them manually (staging first, then `ic`):
|
|
46
|
+
>
|
|
47
|
+
> ```bash
|
|
48
|
+
> NODE_ENV=production dfx deploy --no-wallet --identity mops --network <staging|ic> <canister>
|
|
49
|
+
> ```
|
|
47
50
|
|
|
48
|
-
## 5.
|
|
51
|
+
## 5. Artifacts PR
|
|
49
52
|
|
|
50
|
-
After the
|
|
53
|
+
After the release pipeline completes, it creates and auto-merges a `cli-releases: vX.Y.Z artifacts` PR. No action needed unless it fails — monitor at [Actions → Release CLI](https://github.com/caffeinelabs/mops/actions/workflows/release.yml) and merge the artifacts PR manually if needed.
|
|
51
54
|
|
|
52
55
|
## Verify build
|
|
53
56
|
|
package/bundle/cli.tgz
CHANGED
|
Binary file
|
package/cli.ts
CHANGED
|
@@ -322,7 +322,7 @@ program
|
|
|
322
322
|
program
|
|
323
323
|
.command("check [files...]")
|
|
324
324
|
.description(
|
|
325
|
-
"Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured",
|
|
325
|
+
"Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain] and rule directories are present",
|
|
326
326
|
)
|
|
327
327
|
.option("--verbose", "Verbose console output")
|
|
328
328
|
.addOption(
|
package/commands/check.ts
CHANGED
|
@@ -3,7 +3,12 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import { cliError } from "../error.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
getGlobalMocArgs,
|
|
8
|
+
getRootDir,
|
|
9
|
+
readConfig,
|
|
10
|
+
resolveConfigPath,
|
|
11
|
+
} from "../mops.js";
|
|
7
12
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
8
13
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
14
|
import {
|
|
@@ -13,6 +18,7 @@ import {
|
|
|
13
18
|
import { runStableCheck } from "./check-stable.js";
|
|
14
19
|
import { sourcesArgs } from "./sources.js";
|
|
15
20
|
import { toolchain } from "./toolchain/index.js";
|
|
21
|
+
import { collectLintRules, lint } from "./lint.js";
|
|
16
22
|
|
|
17
23
|
const MOC_ALL_LIBS_MIN_VERSION = "1.3.0";
|
|
18
24
|
|
|
@@ -31,7 +37,8 @@ export async function check(
|
|
|
31
37
|
files: string | string[],
|
|
32
38
|
options: Partial<CheckOptions> = {},
|
|
33
39
|
): Promise<void> {
|
|
34
|
-
|
|
40
|
+
const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
|
|
41
|
+
let fileList = [...explicitFiles];
|
|
35
42
|
|
|
36
43
|
const config = readConfig();
|
|
37
44
|
|
|
@@ -166,4 +173,15 @@ export async function check(
|
|
|
166
173
|
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
167
174
|
});
|
|
168
175
|
}
|
|
176
|
+
|
|
177
|
+
if (config.toolchain?.lintoko) {
|
|
178
|
+
const rootDir = getRootDir();
|
|
179
|
+
const lintRules = await collectLintRules(config, rootDir);
|
|
180
|
+
await lint(undefined, {
|
|
181
|
+
verbose: options.verbose,
|
|
182
|
+
fix: options.fix,
|
|
183
|
+
rules: lintRules,
|
|
184
|
+
files: explicitFiles.length > 0 ? explicitFiles : undefined,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
169
187
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { globSync } from "glob";
|
|
4
|
-
import { JSDOM } from "jsdom";
|
|
5
4
|
import { docs } from "./docs.js";
|
|
6
5
|
|
|
7
6
|
export type DocsCoverageReporter =
|
|
@@ -30,12 +29,12 @@ export async function docsCoverage(options: Partial<DocsCoverageOptions> = {}) {
|
|
|
30
29
|
await docs({
|
|
31
30
|
source,
|
|
32
31
|
output: docsDir,
|
|
33
|
-
format: "
|
|
32
|
+
format: "adoc",
|
|
34
33
|
silent: true,
|
|
35
34
|
});
|
|
36
35
|
|
|
37
|
-
let files = globSync(`${docsDir}/**/*.
|
|
38
|
-
ignore: [`${docsDir}
|
|
36
|
+
let files = globSync(`${docsDir}/**/*.adoc`, {
|
|
37
|
+
ignore: [`${docsDir}/**/*.test.adoc`, `${docsDir}/test/**/*`],
|
|
39
38
|
});
|
|
40
39
|
let coverages = [];
|
|
41
40
|
|
|
@@ -88,35 +87,41 @@ export async function docsCoverage(options: Partial<DocsCoverageOptions> = {}) {
|
|
|
88
87
|
}
|
|
89
88
|
|
|
90
89
|
function docFileCoverage(file: string) {
|
|
91
|
-
let
|
|
90
|
+
let content = readFileSync(file, "utf-8");
|
|
92
91
|
|
|
93
|
-
|
|
92
|
+
// Module name is on the line after the [[module.*]] anchor
|
|
93
|
+
let module =
|
|
94
|
+
content.match(/^\[\[module\.[^\]]+\]\]\n= (.+)$/m)?.[1]?.trim() || "";
|
|
94
95
|
let moduleFile = `${module}.mo`;
|
|
95
96
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
let
|
|
102
|
-
|
|
97
|
+
// Split into per-declaration sections at every [[id]] that is NOT [[module.*]]
|
|
98
|
+
let sections = content.split(/^(?=\[\[(?!module\.))/m).slice(1);
|
|
99
|
+
|
|
100
|
+
let items = sections.map((section) => {
|
|
101
|
+
let rawId = section.match(/^\[\[([^\]]+)\]\]/)?.[1] ?? "";
|
|
102
|
+
let id = rawId.replace(/^type\./, "");
|
|
103
|
+
// mo-doc anchors types as [[type.X]]; classes/values have no prefix → "func"
|
|
104
|
+
let type = rawId.startsWith("type.") ? "type" : "func";
|
|
105
|
+
let definition = section.match(/^== (.+)$/m)?.[1]?.trim() ?? "";
|
|
106
|
+
|
|
107
|
+
// Text after the closing ---- is the doc comment (empty when undocumented).
|
|
108
|
+
// slice(2).join preserves any ---- that appears inside the comment itself.
|
|
109
|
+
let parts = section.split(/^----$/m);
|
|
110
|
+
let comment = parts.slice(2).join("----").trim();
|
|
111
|
+
|
|
103
112
|
return {
|
|
104
113
|
file: moduleFile,
|
|
105
114
|
id,
|
|
106
115
|
type,
|
|
107
116
|
definition,
|
|
108
117
|
comment,
|
|
109
|
-
covered:
|
|
118
|
+
covered: comment.length >= 5,
|
|
110
119
|
};
|
|
111
120
|
});
|
|
112
121
|
|
|
113
|
-
let coverage =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
} else {
|
|
117
|
-
coverage =
|
|
118
|
-
(items.filter((item) => item.covered).length / items.length) * 100;
|
|
119
|
-
}
|
|
122
|
+
let coverage = !items.length
|
|
123
|
+
? 100
|
|
124
|
+
: (items.filter((item) => item.covered).length / items.length) * 100;
|
|
120
125
|
|
|
121
126
|
return { file: moduleFile, coverage, items };
|
|
122
127
|
}
|
package/commands/lint.ts
CHANGED
|
@@ -3,15 +3,103 @@ import { execa } from "execa";
|
|
|
3
3
|
import { globSync } from "glob";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { cliError } from "../error.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
formatDir,
|
|
8
|
+
formatGithubDir,
|
|
9
|
+
getDependencyType,
|
|
10
|
+
getRootDir,
|
|
11
|
+
readConfig,
|
|
12
|
+
} from "../mops.js";
|
|
13
|
+
import { resolvePackages } from "../resolve-packages.js";
|
|
7
14
|
import { toolchain } from "./toolchain/index.js";
|
|
8
15
|
import { MOTOKO_GLOB_CONFIG } from "../constants.js";
|
|
9
16
|
import { existsSync } from "node:fs";
|
|
17
|
+
import { Config } from "../types.js";
|
|
18
|
+
|
|
19
|
+
async function resolveDepRules(
|
|
20
|
+
config: Config,
|
|
21
|
+
rootDir: string,
|
|
22
|
+
): Promise<string[]> {
|
|
23
|
+
const ext = config.lint?.extends;
|
|
24
|
+
if (!ext) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const resolvedPackages = await resolvePackages();
|
|
29
|
+
const rules: string[] = [];
|
|
30
|
+
const matched = new Set<string>();
|
|
31
|
+
const hasRules = new Set<string>();
|
|
32
|
+
|
|
33
|
+
for (const [name, version] of Object.entries(resolvedPackages)) {
|
|
34
|
+
if (ext !== true && !ext.includes(name)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
matched.add(name);
|
|
38
|
+
|
|
39
|
+
const depType = getDependencyType(version);
|
|
40
|
+
let pkgDir: string;
|
|
41
|
+
if (depType === "local") {
|
|
42
|
+
pkgDir = version;
|
|
43
|
+
} else if (depType === "github") {
|
|
44
|
+
pkgDir = formatGithubDir(name, version);
|
|
45
|
+
} else {
|
|
46
|
+
pkgDir = formatDir(name, version);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const rulesDir = path.join(pkgDir, "rules");
|
|
50
|
+
if (existsSync(rulesDir)) {
|
|
51
|
+
rules.push(path.relative(rootDir, rulesDir));
|
|
52
|
+
hasRules.add(name);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (Array.isArray(ext)) {
|
|
57
|
+
const unresolved = ext.filter((n) => !matched.has(n));
|
|
58
|
+
if (unresolved.length > 0) {
|
|
59
|
+
console.warn(
|
|
60
|
+
chalk.yellow(
|
|
61
|
+
`[lint] extends: package(s) not found in dependencies: ${unresolved.join(", ")}`,
|
|
62
|
+
),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const noRulesDir = ext.filter((n) => matched.has(n) && !hasRules.has(n));
|
|
66
|
+
if (noRulesDir.length > 0) {
|
|
67
|
+
console.warn(
|
|
68
|
+
chalk.yellow(
|
|
69
|
+
`[lint] extends: package(s) have no rules/ directory: ${noRulesDir.join(", ")}`,
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return rules;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function collectLintRules(
|
|
79
|
+
config: Config,
|
|
80
|
+
rootDir: string,
|
|
81
|
+
): Promise<string[]> {
|
|
82
|
+
const configRules = config.lint?.rules ?? [];
|
|
83
|
+
for (const d of configRules) {
|
|
84
|
+
if (!existsSync(path.join(rootDir, d))) {
|
|
85
|
+
cliError(
|
|
86
|
+
`[lint] rules: directory '${d}' not found. Check your mops.toml [lint] config.`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const localRules =
|
|
91
|
+
configRules.length > 0
|
|
92
|
+
? configRules
|
|
93
|
+
: ["lint", "lints"].filter((d) => existsSync(path.join(rootDir, d)));
|
|
94
|
+
const depRules = await resolveDepRules(config, rootDir);
|
|
95
|
+
return [...localRules, ...depRules];
|
|
96
|
+
}
|
|
10
97
|
|
|
11
98
|
export interface LintOptions {
|
|
12
99
|
verbose: boolean;
|
|
13
100
|
fix: boolean;
|
|
14
101
|
rules?: string[];
|
|
102
|
+
files?: string[];
|
|
15
103
|
extraArgs: string[];
|
|
16
104
|
}
|
|
17
105
|
|
|
@@ -25,13 +113,22 @@ export async function lint(
|
|
|
25
113
|
? await toolchain.bin("lintoko")
|
|
26
114
|
: "lintoko";
|
|
27
115
|
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
116
|
+
let filesToLint: string[];
|
|
117
|
+
if (options.files && options.files.length > 0) {
|
|
118
|
+
filesToLint = options.files;
|
|
119
|
+
} else {
|
|
120
|
+
let globStr = filter ? `**/*${filter}*.mo` : "**/*.mo";
|
|
121
|
+
filesToLint = globSync(path.join(rootDir, globStr), {
|
|
122
|
+
...MOTOKO_GLOB_CONFIG,
|
|
123
|
+
cwd: rootDir,
|
|
124
|
+
});
|
|
125
|
+
if (filesToLint.length === 0) {
|
|
126
|
+
cliError(
|
|
127
|
+
filter
|
|
128
|
+
? `No files found for filter '${filter}'`
|
|
129
|
+
: "No .mo files found in the project",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
35
132
|
}
|
|
36
133
|
|
|
37
134
|
let args: string[] = [];
|
|
@@ -42,9 +139,9 @@ export async function lint(
|
|
|
42
139
|
args.push("--fix");
|
|
43
140
|
}
|
|
44
141
|
const rules =
|
|
45
|
-
options.rules
|
|
142
|
+
options.rules !== undefined
|
|
46
143
|
? options.rules
|
|
47
|
-
:
|
|
144
|
+
: await collectLintRules(config, rootDir);
|
|
48
145
|
rules.forEach((rule) => args.push("--rules", rule));
|
|
49
146
|
|
|
50
147
|
if (config.lint?.args) {
|
package/commands/publish.ts
CHANGED
|
@@ -83,7 +83,7 @@ export async function publish(
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
// desired fields
|
|
86
|
-
for (let key of ["description"
|
|
86
|
+
for (let key of ["description"]) {
|
|
87
87
|
// @ts-ignore
|
|
88
88
|
if (!config.package[key] && !process.env.CI) {
|
|
89
89
|
let res = await prompts({
|
|
@@ -120,6 +120,14 @@ export async function publish(
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// disabled fields
|
|
124
|
+
for (let key of ["dfx", "moc", "homepage", "documentation", "donation"]) {
|
|
125
|
+
if ((config.package as any)[key]) {
|
|
126
|
+
console.log(chalk.red("Error: ") + `package.${key} is not supported yet`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
123
131
|
// check lengths
|
|
124
132
|
let keysMax = {
|
|
125
133
|
name: 50,
|
|
@@ -130,7 +138,7 @@ export async function publish(
|
|
|
130
138
|
documentation: 300,
|
|
131
139
|
homepage: 300,
|
|
132
140
|
readme: 100,
|
|
133
|
-
license:
|
|
141
|
+
license: 40,
|
|
134
142
|
files: 20,
|
|
135
143
|
dfx: 10,
|
|
136
144
|
moc: 10,
|
|
@@ -166,18 +174,12 @@ export async function publish(
|
|
|
166
174
|
}
|
|
167
175
|
|
|
168
176
|
for (let dep of Object.values(config.dependencies)) {
|
|
169
|
-
if (dep.repo
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
"GitHub dependencies make the registry less reliable and limit its capabilities.\nIf you are the owner of the dependency, please consider publishing it to the Mops registry.",
|
|
176
|
-
) + "\n\nPublish anyway?",
|
|
177
|
-
});
|
|
178
|
-
if (!res.ok) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
177
|
+
if (dep.repo) {
|
|
178
|
+
console.log(
|
|
179
|
+
chalk.red("Error: ") +
|
|
180
|
+
"GitHub dependencies are no longer supported.\nIf you are the owner of the dependency, please publish it to the Mops registry.",
|
|
181
|
+
);
|
|
182
|
+
process.exit(1);
|
|
181
183
|
}
|
|
182
184
|
}
|
|
183
185
|
}
|
|
@@ -271,6 +273,7 @@ export async function publish(
|
|
|
271
273
|
"README.md",
|
|
272
274
|
"LICENSE",
|
|
273
275
|
"NOTICE",
|
|
276
|
+
"rules/*.toml",
|
|
274
277
|
"!.mops/**",
|
|
275
278
|
"!test/**",
|
|
276
279
|
"!tests/**",
|
|
@@ -331,10 +334,23 @@ export async function publish(
|
|
|
331
334
|
}
|
|
332
335
|
}
|
|
333
336
|
|
|
337
|
+
// pre-flight file count check (must match MAX_PACKAGE_FILES in PackagePublisher.mo)
|
|
338
|
+
const FILE_LIMIT = 1000;
|
|
339
|
+
if (files.length > FILE_LIMIT) {
|
|
340
|
+
console.log(
|
|
341
|
+
chalk.red("Error: ") +
|
|
342
|
+
`Too many files (${files.length}). Maximum is ${FILE_LIMIT}.`,
|
|
343
|
+
);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
|
|
334
347
|
// parse changelog
|
|
335
348
|
console.log("Parsing CHANGELOG.md...");
|
|
336
349
|
let changelog = parseChangelog(config.package.version);
|
|
337
|
-
if (
|
|
350
|
+
if (
|
|
351
|
+
!changelog &&
|
|
352
|
+
config.package.repository?.startsWith("https://github.com/")
|
|
353
|
+
) {
|
|
338
354
|
console.log("Fetching release notes from GitHub...");
|
|
339
355
|
changelog = await fetchGitHubReleaseNotes(
|
|
340
356
|
config.package.repository,
|
|
@@ -513,6 +529,8 @@ function parseChangelog(version: string): string {
|
|
|
513
529
|
return changelog || "";
|
|
514
530
|
}
|
|
515
531
|
|
|
532
|
+
type GitHubRelease = { message?: string; body?: string };
|
|
533
|
+
|
|
516
534
|
async function fetchGitHubReleaseNotes(
|
|
517
535
|
repo: string,
|
|
518
536
|
version: string,
|
|
@@ -521,13 +539,13 @@ async function fetchGitHubReleaseNotes(
|
|
|
521
539
|
let res = await fetch(
|
|
522
540
|
`https://api.github.com/repos${repoPath}/releases/tags/${version}`,
|
|
523
541
|
);
|
|
524
|
-
let release = await res.json();
|
|
542
|
+
let release = (await res.json()) as GitHubRelease;
|
|
525
543
|
|
|
526
544
|
if (release.message === "Not Found") {
|
|
527
545
|
res = await fetch(
|
|
528
546
|
`https://api.github.com/repos${repoPath}/releases/tags/v${version}`,
|
|
529
547
|
);
|
|
530
|
-
release = await res.json();
|
|
548
|
+
release = (await res.json()) as GitHubRelease;
|
|
531
549
|
|
|
532
550
|
if (release.message === "Not Found") {
|
|
533
551
|
console.log(
|
|
@@ -539,5 +557,5 @@ async function fetchGitHubReleaseNotes(
|
|
|
539
557
|
}
|
|
540
558
|
}
|
|
541
559
|
|
|
542
|
-
return release.body;
|
|
560
|
+
return release.body ?? "";
|
|
543
561
|
}
|
package/commands/self.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -255,7 +255,7 @@ program
|
|
|
255
255
|
// check
|
|
256
256
|
program
|
|
257
257
|
.command("check [files...]")
|
|
258
|
-
.description("Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured")
|
|
258
|
+
.description("Check Motoko files for syntax errors and type issues. If no files are specified, checks all canister entrypoints from mops.toml. Also runs stable compatibility checks for canisters with [check-stable] configured, and runs linting if lintoko is configured in [toolchain] and rule directories are present")
|
|
259
259
|
.option("--verbose", "Verbose console output")
|
|
260
260
|
.addOption(new Option("--fix", "Apply autofixes to all files, including transitively imported ones"))
|
|
261
261
|
.allowUnknownOption(true)
|
package/dist/commands/check.js
CHANGED
|
@@ -3,20 +3,22 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { execa } from "execa";
|
|
5
5
|
import { cliError } from "../error.js";
|
|
6
|
-
import { getGlobalMocArgs, readConfig, resolveConfigPath } from "../mops.js";
|
|
6
|
+
import { getGlobalMocArgs, getRootDir, readConfig, resolveConfigPath, } from "../mops.js";
|
|
7
7
|
import { autofixMotoko } from "../helpers/autofix-motoko.js";
|
|
8
8
|
import { getMocSemVer } from "../helpers/get-moc-version.js";
|
|
9
9
|
import { resolveCanisterConfigs, resolveCanisterEntrypoints, } from "../helpers/resolve-canisters.js";
|
|
10
10
|
import { runStableCheck } from "./check-stable.js";
|
|
11
11
|
import { sourcesArgs } from "./sources.js";
|
|
12
12
|
import { toolchain } from "./toolchain/index.js";
|
|
13
|
+
import { collectLintRules, lint } from "./lint.js";
|
|
13
14
|
const MOC_ALL_LIBS_MIN_VERSION = "1.3.0";
|
|
14
15
|
function supportsAllLibsFlag() {
|
|
15
16
|
const version = getMocSemVer();
|
|
16
17
|
return version ? version.compare(MOC_ALL_LIBS_MIN_VERSION) >= 0 : false;
|
|
17
18
|
}
|
|
18
19
|
export async function check(files, options = {}) {
|
|
19
|
-
|
|
20
|
+
const explicitFiles = Array.isArray(files) ? files : files ? [files] : [];
|
|
21
|
+
let fileList = [...explicitFiles];
|
|
20
22
|
const config = readConfig();
|
|
21
23
|
if (fileList.length === 0) {
|
|
22
24
|
fileList = resolveCanisterEntrypoints(config).map(resolveConfigPath);
|
|
@@ -115,4 +117,14 @@ export async function check(files, options = {}) {
|
|
|
115
117
|
options: { verbose: options.verbose, extraArgs: options.extraArgs },
|
|
116
118
|
});
|
|
117
119
|
}
|
|
120
|
+
if (config.toolchain?.lintoko) {
|
|
121
|
+
const rootDir = getRootDir();
|
|
122
|
+
const lintRules = await collectLintRules(config, rootDir);
|
|
123
|
+
await lint(undefined, {
|
|
124
|
+
verbose: options.verbose,
|
|
125
|
+
fix: options.fix,
|
|
126
|
+
rules: lintRules,
|
|
127
|
+
files: explicitFiles.length > 0 ? explicitFiles : undefined,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
118
130
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { globSync } from "glob";
|
|
4
|
-
import { JSDOM } from "jsdom";
|
|
5
4
|
import { docs } from "./docs.js";
|
|
6
5
|
export async function docsCoverage(options = {}) {
|
|
7
6
|
let docsDir = ".mops/.docs";
|
|
@@ -14,11 +13,11 @@ export async function docsCoverage(options = {}) {
|
|
|
14
13
|
await docs({
|
|
15
14
|
source,
|
|
16
15
|
output: docsDir,
|
|
17
|
-
format: "
|
|
16
|
+
format: "adoc",
|
|
18
17
|
silent: true,
|
|
19
18
|
});
|
|
20
|
-
let files = globSync(`${docsDir}/**/*.
|
|
21
|
-
ignore: [`${docsDir}
|
|
19
|
+
let files = globSync(`${docsDir}/**/*.adoc`, {
|
|
20
|
+
ignore: [`${docsDir}/**/*.test.adoc`, `${docsDir}/test/**/*`],
|
|
22
21
|
});
|
|
23
22
|
let coverages = [];
|
|
24
23
|
for (let file of files) {
|
|
@@ -58,33 +57,34 @@ export async function docsCoverage(options = {}) {
|
|
|
58
57
|
return totalCoverage;
|
|
59
58
|
}
|
|
60
59
|
function docFileCoverage(file) {
|
|
61
|
-
let
|
|
62
|
-
|
|
60
|
+
let content = readFileSync(file, "utf-8");
|
|
61
|
+
// Module name is on the line after the [[module.*]] anchor
|
|
62
|
+
let module = content.match(/^\[\[module\.[^\]]+\]\]\n= (.+)$/m)?.[1]?.trim() || "";
|
|
63
63
|
let moduleFile = `${module}.mo`;
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
let
|
|
64
|
+
// Split into per-declaration sections at every [[id]] that is NOT [[module.*]]
|
|
65
|
+
let sections = content.split(/^(?=\[\[(?!module\.))/m).slice(1);
|
|
66
|
+
let items = sections.map((section) => {
|
|
67
|
+
let rawId = section.match(/^\[\[([^\]]+)\]\]/)?.[1] ?? "";
|
|
68
|
+
let id = rawId.replace(/^type\./, "");
|
|
69
|
+
// mo-doc anchors types as [[type.X]]; classes/values have no prefix → "func"
|
|
70
|
+
let type = rawId.startsWith("type.") ? "type" : "func";
|
|
71
|
+
let definition = section.match(/^== (.+)$/m)?.[1]?.trim() ?? "";
|
|
72
|
+
// Text after the closing ---- is the doc comment (empty when undocumented).
|
|
73
|
+
// slice(2).join preserves any ---- that appears inside the comment itself.
|
|
74
|
+
let parts = section.split(/^----$/m);
|
|
75
|
+
let comment = parts.slice(2).join("----").trim();
|
|
71
76
|
return {
|
|
72
77
|
file: moduleFile,
|
|
73
78
|
id,
|
|
74
79
|
type,
|
|
75
80
|
definition,
|
|
76
81
|
comment,
|
|
77
|
-
covered:
|
|
82
|
+
covered: comment.length >= 5,
|
|
78
83
|
};
|
|
79
84
|
});
|
|
80
|
-
let coverage =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
coverage =
|
|
86
|
-
(items.filter((item) => item.covered).length / items.length) * 100;
|
|
87
|
-
}
|
|
85
|
+
let coverage = !items.length
|
|
86
|
+
? 100
|
|
87
|
+
: (items.filter((item) => item.covered).length / items.length) * 100;
|
|
88
88
|
return { file: moduleFile, coverage, items };
|
|
89
89
|
}
|
|
90
90
|
function colorizeCoverage(coverage) {
|
package/dist/commands/lint.d.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { Config } from "../types.js";
|
|
2
|
+
export declare function collectLintRules(config: Config, rootDir: string): Promise<string[]>;
|
|
1
3
|
export interface LintOptions {
|
|
2
4
|
verbose: boolean;
|
|
3
5
|
fix: boolean;
|
|
4
6
|
rules?: string[];
|
|
7
|
+
files?: string[];
|
|
5
8
|
extraArgs: string[];
|
|
6
9
|
}
|
|
7
10
|
export declare function lint(filter: string | undefined, options: Partial<LintOptions>): Promise<void>;
|
package/dist/commands/lint.js
CHANGED
|
@@ -3,23 +3,88 @@ import { execa } from "execa";
|
|
|
3
3
|
import { globSync } from "glob";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { cliError } from "../error.js";
|
|
6
|
-
import { getRootDir, readConfig } from "../mops.js";
|
|
6
|
+
import { formatDir, formatGithubDir, getDependencyType, getRootDir, readConfig, } from "../mops.js";
|
|
7
|
+
import { resolvePackages } from "../resolve-packages.js";
|
|
7
8
|
import { toolchain } from "./toolchain/index.js";
|
|
8
9
|
import { MOTOKO_GLOB_CONFIG } from "../constants.js";
|
|
9
10
|
import { existsSync } from "node:fs";
|
|
11
|
+
async function resolveDepRules(config, rootDir) {
|
|
12
|
+
const ext = config.lint?.extends;
|
|
13
|
+
if (!ext) {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const resolvedPackages = await resolvePackages();
|
|
17
|
+
const rules = [];
|
|
18
|
+
const matched = new Set();
|
|
19
|
+
const hasRules = new Set();
|
|
20
|
+
for (const [name, version] of Object.entries(resolvedPackages)) {
|
|
21
|
+
if (ext !== true && !ext.includes(name)) {
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
matched.add(name);
|
|
25
|
+
const depType = getDependencyType(version);
|
|
26
|
+
let pkgDir;
|
|
27
|
+
if (depType === "local") {
|
|
28
|
+
pkgDir = version;
|
|
29
|
+
}
|
|
30
|
+
else if (depType === "github") {
|
|
31
|
+
pkgDir = formatGithubDir(name, version);
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
pkgDir = formatDir(name, version);
|
|
35
|
+
}
|
|
36
|
+
const rulesDir = path.join(pkgDir, "rules");
|
|
37
|
+
if (existsSync(rulesDir)) {
|
|
38
|
+
rules.push(path.relative(rootDir, rulesDir));
|
|
39
|
+
hasRules.add(name);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(ext)) {
|
|
43
|
+
const unresolved = ext.filter((n) => !matched.has(n));
|
|
44
|
+
if (unresolved.length > 0) {
|
|
45
|
+
console.warn(chalk.yellow(`[lint] extends: package(s) not found in dependencies: ${unresolved.join(", ")}`));
|
|
46
|
+
}
|
|
47
|
+
const noRulesDir = ext.filter((n) => matched.has(n) && !hasRules.has(n));
|
|
48
|
+
if (noRulesDir.length > 0) {
|
|
49
|
+
console.warn(chalk.yellow(`[lint] extends: package(s) have no rules/ directory: ${noRulesDir.join(", ")}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return rules;
|
|
53
|
+
}
|
|
54
|
+
export async function collectLintRules(config, rootDir) {
|
|
55
|
+
const configRules = config.lint?.rules ?? [];
|
|
56
|
+
for (const d of configRules) {
|
|
57
|
+
if (!existsSync(path.join(rootDir, d))) {
|
|
58
|
+
cliError(`[lint] rules: directory '${d}' not found. Check your mops.toml [lint] config.`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const localRules = configRules.length > 0
|
|
62
|
+
? configRules
|
|
63
|
+
: ["lint", "lints"].filter((d) => existsSync(path.join(rootDir, d)));
|
|
64
|
+
const depRules = await resolveDepRules(config, rootDir);
|
|
65
|
+
return [...localRules, ...depRules];
|
|
66
|
+
}
|
|
10
67
|
export async function lint(filter, options) {
|
|
11
68
|
let config = readConfig();
|
|
12
69
|
let rootDir = getRootDir();
|
|
13
70
|
let lintokoBinPath = config.toolchain?.lintoko
|
|
14
71
|
? await toolchain.bin("lintoko")
|
|
15
72
|
: "lintoko";
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
73
|
+
let filesToLint;
|
|
74
|
+
if (options.files && options.files.length > 0) {
|
|
75
|
+
filesToLint = options.files;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
let globStr = filter ? `**/*${filter}*.mo` : "**/*.mo";
|
|
79
|
+
filesToLint = globSync(path.join(rootDir, globStr), {
|
|
80
|
+
...MOTOKO_GLOB_CONFIG,
|
|
81
|
+
cwd: rootDir,
|
|
82
|
+
});
|
|
83
|
+
if (filesToLint.length === 0) {
|
|
84
|
+
cliError(filter
|
|
85
|
+
? `No files found for filter '${filter}'`
|
|
86
|
+
: "No .mo files found in the project");
|
|
87
|
+
}
|
|
23
88
|
}
|
|
24
89
|
let args = [];
|
|
25
90
|
if (options.verbose) {
|
|
@@ -28,9 +93,9 @@ export async function lint(filter, options) {
|
|
|
28
93
|
if (options.fix) {
|
|
29
94
|
args.push("--fix");
|
|
30
95
|
}
|
|
31
|
-
const rules = options.rules
|
|
96
|
+
const rules = options.rules !== undefined
|
|
32
97
|
? options.rules
|
|
33
|
-
:
|
|
98
|
+
: await collectLintRules(config, rootDir);
|
|
34
99
|
rules.forEach((rule) => args.push("--rules", rule));
|
|
35
100
|
if (config.lint?.args) {
|
|
36
101
|
if (typeof config.lint.args === "string") {
|
package/dist/commands/publish.js
CHANGED
|
@@ -50,7 +50,7 @@ export async function publish(options = {}) {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
// desired fields
|
|
53
|
-
for (let key of ["description"
|
|
53
|
+
for (let key of ["description"]) {
|
|
54
54
|
// @ts-ignore
|
|
55
55
|
if (!config.package[key] && !process.env.CI) {
|
|
56
56
|
let res = await prompts({
|
|
@@ -85,6 +85,13 @@ export async function publish(options = {}) {
|
|
|
85
85
|
process.exit(1);
|
|
86
86
|
}
|
|
87
87
|
}
|
|
88
|
+
// disabled fields
|
|
89
|
+
for (let key of ["dfx", "moc", "homepage", "documentation", "donation"]) {
|
|
90
|
+
if (config.package[key]) {
|
|
91
|
+
console.log(chalk.red("Error: ") + `package.${key} is not supported yet`);
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
88
95
|
// check lengths
|
|
89
96
|
let keysMax = {
|
|
90
97
|
name: 50,
|
|
@@ -95,7 +102,7 @@ export async function publish(options = {}) {
|
|
|
95
102
|
documentation: 300,
|
|
96
103
|
homepage: 300,
|
|
97
104
|
readme: 100,
|
|
98
|
-
license:
|
|
105
|
+
license: 40,
|
|
99
106
|
files: 20,
|
|
100
107
|
dfx: 10,
|
|
101
108
|
moc: 10,
|
|
@@ -123,15 +130,10 @@ export async function publish(options = {}) {
|
|
|
123
130
|
delete dep.path;
|
|
124
131
|
}
|
|
125
132
|
for (let dep of Object.values(config.dependencies)) {
|
|
126
|
-
if (dep.repo
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
message: chalk.yellow("GitHub dependencies make the registry less reliable and limit its capabilities.\nIf you are the owner of the dependency, please consider publishing it to the Mops registry.") + "\n\nPublish anyway?",
|
|
131
|
-
});
|
|
132
|
-
if (!res.ok) {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
133
|
+
if (dep.repo) {
|
|
134
|
+
console.log(chalk.red("Error: ") +
|
|
135
|
+
"GitHub dependencies are no longer supported.\nIf you are the owner of the dependency, please publish it to the Mops registry.");
|
|
136
|
+
process.exit(1);
|
|
135
137
|
}
|
|
136
138
|
}
|
|
137
139
|
}
|
|
@@ -210,6 +212,7 @@ export async function publish(options = {}) {
|
|
|
210
212
|
"README.md",
|
|
211
213
|
"LICENSE",
|
|
212
214
|
"NOTICE",
|
|
215
|
+
"rules/*.toml",
|
|
213
216
|
"!.mops/**",
|
|
214
217
|
"!test/**",
|
|
215
218
|
"!tests/**",
|
|
@@ -261,10 +264,18 @@ export async function publish(options = {}) {
|
|
|
261
264
|
process.exit(1);
|
|
262
265
|
}
|
|
263
266
|
}
|
|
267
|
+
// pre-flight file count check (must match MAX_PACKAGE_FILES in PackagePublisher.mo)
|
|
268
|
+
const FILE_LIMIT = 1000;
|
|
269
|
+
if (files.length > FILE_LIMIT) {
|
|
270
|
+
console.log(chalk.red("Error: ") +
|
|
271
|
+
`Too many files (${files.length}). Maximum is ${FILE_LIMIT}.`);
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
264
274
|
// parse changelog
|
|
265
275
|
console.log("Parsing CHANGELOG.md...");
|
|
266
276
|
let changelog = parseChangelog(config.package.version);
|
|
267
|
-
if (!changelog &&
|
|
277
|
+
if (!changelog &&
|
|
278
|
+
config.package.repository?.startsWith("https://github.com/")) {
|
|
268
279
|
console.log("Fetching release notes from GitHub...");
|
|
269
280
|
changelog = await fetchGitHubReleaseNotes(config.package.repository, config.package.version);
|
|
270
281
|
}
|
|
@@ -402,14 +413,14 @@ function parseChangelog(version) {
|
|
|
402
413
|
async function fetchGitHubReleaseNotes(repo, version) {
|
|
403
414
|
let repoPath = new URL(repo).pathname;
|
|
404
415
|
let res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/${version}`);
|
|
405
|
-
let release = await res.json();
|
|
416
|
+
let release = (await res.json());
|
|
406
417
|
if (release.message === "Not Found") {
|
|
407
418
|
res = await fetch(`https://api.github.com/repos${repoPath}/releases/tags/v${version}`);
|
|
408
|
-
release = await res.json();
|
|
419
|
+
release = (await res.json());
|
|
409
420
|
if (release.message === "Not Found") {
|
|
410
421
|
console.log(chalk.yellow(`No GitHub release found with name ${version} or v${version}`));
|
|
411
422
|
return "";
|
|
412
423
|
}
|
|
413
424
|
}
|
|
414
|
-
return release.body;
|
|
425
|
+
return release.body ?? "";
|
|
415
426
|
}
|
package/dist/commands/self.js
CHANGED
|
@@ -27,7 +27,7 @@ function detectPackageManager() {
|
|
|
27
27
|
}
|
|
28
28
|
export async function getLatestVersion() {
|
|
29
29
|
let res = await fetch(url + "/tags/latest");
|
|
30
|
-
return res.text();
|
|
30
|
+
return (await res.text()).trim();
|
|
31
31
|
}
|
|
32
32
|
export async function update() {
|
|
33
33
|
let latest = await getLatestVersion();
|
package/dist/package.json
CHANGED
package/dist/release-cli.js
CHANGED
|
@@ -29,7 +29,7 @@ fs.cpSync(path.resolve(__dirname, `../cli-releases/versions/${version}.tgz`), pa
|
|
|
29
29
|
fs.writeFileSync(path.resolve(__dirname, `../cli-releases/tags/${tag}`), version);
|
|
30
30
|
console.log(`Release '${version}' created with tag '${tag}'`);
|
|
31
31
|
if (!fs.existsSync(path.resolve(__dirname, "../cli-releases/releases.json"))) {
|
|
32
|
-
fs.writeFileSync(path.resolve(__dirname, "../cli-releases/releases.json"), JSON.stringify({ tags: {}, versions: {} }, null, 2));
|
|
32
|
+
fs.writeFileSync(path.resolve(__dirname, "../cli-releases/releases.json"), JSON.stringify({ tags: {}, versions: {} }, null, 2) + "\n");
|
|
33
33
|
}
|
|
34
34
|
let releases = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../cli-releases/releases.json"), "utf8"));
|
|
35
35
|
releases.tags[tag] = version;
|
|
@@ -41,4 +41,4 @@ releases.versions[version] = {
|
|
|
41
41
|
url: `https://cli.mops.one/versions/${version}.tgz`,
|
|
42
42
|
hash,
|
|
43
43
|
};
|
|
44
|
-
fs.writeFileSync(path.resolve(__dirname, "../cli-releases/releases.json"), JSON.stringify(releases, null, 2));
|
|
44
|
+
fs.writeFileSync(path.resolve(__dirname, "../cli-releases/releases.json"), JSON.stringify(releases, null, 2) + "\n");
|
package/dist/tests/check.test.js
CHANGED
|
@@ -94,4 +94,28 @@ describe("check", () => {
|
|
|
94
94
|
expect(result.stderr).toMatch(/error/i);
|
|
95
95
|
expect(result.stdout).not.toMatch(/Stable compatibility/);
|
|
96
96
|
});
|
|
97
|
+
test("lint runs after moc check and passes", async () => {
|
|
98
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-pass");
|
|
99
|
+
const result = await cli(["check"], { cwd });
|
|
100
|
+
expect(result.exitCode).toBe(0);
|
|
101
|
+
expect(result.stdout).toMatch(/✓ Lint succeeded/);
|
|
102
|
+
});
|
|
103
|
+
test("check fails when lint finds errors", async () => {
|
|
104
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-fail");
|
|
105
|
+
const result = await cli(["check"], { cwd });
|
|
106
|
+
expect(result.exitCode).toBe(1);
|
|
107
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
108
|
+
});
|
|
109
|
+
test("lint is skipped when lintoko not configured and no rules exist", async () => {
|
|
110
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
111
|
+
const result = await cli(["check"], { cwd });
|
|
112
|
+
expect(result.exitCode).toBe(0);
|
|
113
|
+
expect(result.stdout).not.toMatch(/Lint/);
|
|
114
|
+
});
|
|
115
|
+
test("--fix flag reaches lint step", async () => {
|
|
116
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-pass");
|
|
117
|
+
const result = await cli(["check", "--fix"], { cwd });
|
|
118
|
+
expect(result.exitCode).toBe(0);
|
|
119
|
+
expect(result.stdout).toMatch(/✓ Lint fixes applied/);
|
|
120
|
+
});
|
|
97
121
|
});
|
package/dist/tests/lint.test.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, test } from "@jest/globals";
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { cliSnapshot } from "./helpers";
|
|
3
|
+
import { cli, cliSnapshot } from "./helpers";
|
|
4
4
|
describe("lint", () => {
|
|
5
5
|
test("ok", async () => {
|
|
6
6
|
const cwd = path.join(import.meta.dirname, "lint");
|
|
@@ -12,4 +12,30 @@ describe("lint", () => {
|
|
|
12
12
|
await cliSnapshot(["lint", "NoBoolSwitch", "--verbose"], { cwd }, 1);
|
|
13
13
|
await cliSnapshot(["lint", "DoesNotExist"], { cwd }, 1);
|
|
14
14
|
});
|
|
15
|
+
test("[lint] rules - additional config rules directory is used", async () => {
|
|
16
|
+
const cwd = path.join(import.meta.dirname, "lint-config-rules");
|
|
17
|
+
const result = await cli(["lint"], { cwd });
|
|
18
|
+
expect(result.exitCode).toBe(1);
|
|
19
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
20
|
+
});
|
|
21
|
+
test("[lint] extends - picks up rules/ from named dependency", async () => {
|
|
22
|
+
const cwd = path.join(import.meta.dirname, "lint-extends");
|
|
23
|
+
const result = await cli(["lint"], { cwd });
|
|
24
|
+
expect(result.exitCode).toBe(1);
|
|
25
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
26
|
+
});
|
|
27
|
+
test("[lint] extends true - picks up rules/ from all dependencies", async () => {
|
|
28
|
+
const cwd = path.join(import.meta.dirname, "lint-extends-all");
|
|
29
|
+
const result = await cli(["lint"], { cwd });
|
|
30
|
+
expect(result.exitCode).toBe(1);
|
|
31
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
32
|
+
});
|
|
33
|
+
test("[lint] extends - dep not in extends list is ignored", async () => {
|
|
34
|
+
// my-pkg has rules/ but extends only lists "other-pkg" (which doesn't exist),
|
|
35
|
+
// so no dep rules are loaded and NoBoolSwitch.mo passes with exit 0.
|
|
36
|
+
const cwd = path.join(import.meta.dirname, "lint-extends-ignored");
|
|
37
|
+
const result = await cli(["lint"], { cwd });
|
|
38
|
+
expect(result.exitCode).toBe(0);
|
|
39
|
+
expect(result.stderr).toMatch(/not found in dependencies/);
|
|
40
|
+
});
|
|
15
41
|
});
|
package/dist/types.d.ts
CHANGED
package/package.json
CHANGED
package/release-cli.ts
CHANGED
|
@@ -79,7 +79,7 @@ type Releases = {
|
|
|
79
79
|
if (!fs.existsSync(path.resolve(__dirname, "../cli-releases/releases.json"))) {
|
|
80
80
|
fs.writeFileSync(
|
|
81
81
|
path.resolve(__dirname, "../cli-releases/releases.json"),
|
|
82
|
-
JSON.stringify({ tags: {}, versions: {} }, null, 2),
|
|
82
|
+
JSON.stringify({ tags: {}, versions: {} }, null, 2) + "\n",
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -102,5 +102,5 @@ releases.versions[version] = {
|
|
|
102
102
|
|
|
103
103
|
fs.writeFileSync(
|
|
104
104
|
path.resolve(__dirname, "../cli-releases/releases.json"),
|
|
105
|
-
JSON.stringify(releases, null, 2),
|
|
105
|
+
JSON.stringify(releases, null, 2) + "\n",
|
|
106
106
|
);
|
package/tests/check.test.ts
CHANGED
|
@@ -121,4 +121,32 @@ describe("check", () => {
|
|
|
121
121
|
expect(result.stderr).toMatch(/error/i);
|
|
122
122
|
expect(result.stdout).not.toMatch(/Stable compatibility/);
|
|
123
123
|
});
|
|
124
|
+
|
|
125
|
+
test("lint runs after moc check and passes", async () => {
|
|
126
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-pass");
|
|
127
|
+
const result = await cli(["check"], { cwd });
|
|
128
|
+
expect(result.exitCode).toBe(0);
|
|
129
|
+
expect(result.stdout).toMatch(/✓ Lint succeeded/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("check fails when lint finds errors", async () => {
|
|
133
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-fail");
|
|
134
|
+
const result = await cli(["check"], { cwd });
|
|
135
|
+
expect(result.exitCode).toBe(1);
|
|
136
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("lint is skipped when lintoko not configured and no rules exist", async () => {
|
|
140
|
+
const cwd = path.join(import.meta.dirname, "check/canisters");
|
|
141
|
+
const result = await cli(["check"], { cwd });
|
|
142
|
+
expect(result.exitCode).toBe(0);
|
|
143
|
+
expect(result.stdout).not.toMatch(/Lint/);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("--fix flag reaches lint step", async () => {
|
|
147
|
+
const cwd = path.join(import.meta.dirname, "check/with-lint-pass");
|
|
148
|
+
const result = await cli(["check", "--fix"], { cwd });
|
|
149
|
+
expect(result.exitCode).toBe(0);
|
|
150
|
+
expect(result.stdout).toMatch(/✓ Lint fixes applied/);
|
|
151
|
+
});
|
|
124
152
|
});
|
package/tests/lint.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { describe, test } from "@jest/globals";
|
|
1
|
+
import { describe, expect, test } from "@jest/globals";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { cliSnapshot } from "./helpers";
|
|
3
|
+
import { cli, cliSnapshot } from "./helpers";
|
|
4
4
|
|
|
5
5
|
describe("lint", () => {
|
|
6
6
|
test("ok", async () => {
|
|
@@ -14,4 +14,34 @@ describe("lint", () => {
|
|
|
14
14
|
await cliSnapshot(["lint", "NoBoolSwitch", "--verbose"], { cwd }, 1);
|
|
15
15
|
await cliSnapshot(["lint", "DoesNotExist"], { cwd }, 1);
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
test("[lint] rules - additional config rules directory is used", async () => {
|
|
19
|
+
const cwd = path.join(import.meta.dirname, "lint-config-rules");
|
|
20
|
+
const result = await cli(["lint"], { cwd });
|
|
21
|
+
expect(result.exitCode).toBe(1);
|
|
22
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("[lint] extends - picks up rules/ from named dependency", async () => {
|
|
26
|
+
const cwd = path.join(import.meta.dirname, "lint-extends");
|
|
27
|
+
const result = await cli(["lint"], { cwd });
|
|
28
|
+
expect(result.exitCode).toBe(1);
|
|
29
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("[lint] extends true - picks up rules/ from all dependencies", async () => {
|
|
33
|
+
const cwd = path.join(import.meta.dirname, "lint-extends-all");
|
|
34
|
+
const result = await cli(["lint"], { cwd });
|
|
35
|
+
expect(result.exitCode).toBe(1);
|
|
36
|
+
expect(result.stderr).toMatch(/no-bool-switch/);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("[lint] extends - dep not in extends list is ignored", async () => {
|
|
40
|
+
// my-pkg has rules/ but extends only lists "other-pkg" (which doesn't exist),
|
|
41
|
+
// so no dep rules are loaded and NoBoolSwitch.mo passes with exit 0.
|
|
42
|
+
const cwd = path.join(import.meta.dirname, "lint-extends-ignored");
|
|
43
|
+
const result = await cli(["lint"], { cwd });
|
|
44
|
+
expect(result.exitCode).toBe(0);
|
|
45
|
+
expect(result.stderr).toMatch(/not found in dependencies/);
|
|
46
|
+
});
|
|
17
47
|
});
|