@starci/hfs 5.0.7 → 5.0.8
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 +11 -0
- package/README.md +7 -8
- package/cache/fake-session.mjs +1 -1
- package/cache/hfs-family.mjs +22 -9
- package/cache/import-graph.d.mts +32 -4
- package/cache/import-graph.mjs +143 -11
- package/cache/lint-family.d.mts +0 -2
- package/cache/lint-family.mjs +54 -33
- package/cache/records.d.mts +7 -0
- package/cache/records.mjs +17 -1
- package/cache/run.d.mts +1 -1
- package/cache/session.mjs +6 -2
- package/cache/side-key.d.mts +2 -0
- package/cache/side-key.mjs +5 -1
- package/cache/structure-memo.d.mts +34 -0
- package/cache/structure-memo.mjs +150 -0
- package/cache/types.d.mts +6 -2
- package/cache/wiring-family.mjs +1 -1
- package/codemod/small-renames.mjs +22 -2
- package/lint/eslint-list.cjs +26 -0
- package/lint/run.d.mts +18 -58
- package/lint/run.mjs +54 -284
- package/package.json +1 -1
- package/release/lint-report.d.mts +1 -1
- package/release/lint-report.mjs +3 -3
- package/report/sonar.d.mts +1 -1
- package/report/sonar.mjs +3 -3
- package/report/stylelint-findings.d.mts +23 -0
- package/report/stylelint-findings.mjs +9 -0
- package/runtime/knowledge/hfs/canon-pins.yaml +7 -7
- package/runtime/knowledge/hfs/ci-standard.yaml +2 -2
- package/runtime/knowledge/hfs/rules.yaml +4 -4
- package/runtime/knowledge/hfs/slots.yaml +1 -1
- package/runtime/knowledge/sonar-gate.yaml +2 -2
- package/runtime/modules/kernel/failure-codes.yaml +4 -4
- package/runtime/scripts/api/node/run-node.d.mts +5 -0
- package/runtime/scripts/hfs/check.d.mts +16 -1
- package/runtime/scripts/hfs/check.mjs +18 -7
- package/runtime/scripts/hfs/gate-threshold.mjs +1 -1
- package/runtime/scripts/hfs/rule-memo.d.mts +30 -0
- package/runtime/scripts/hfs/rule-memo.mjs +24 -0
- package/runtime/scripts/hfs/rules/a15-findings.d.mts +6 -0
- package/runtime/scripts/hfs/rules/a15-findings.mjs +11 -2
- package/runtime/scripts/hfs/rules/ci-standard.mjs +2 -2
- package/runtime/scripts/hfs/rules/monorepo.d.mts +1 -3
- package/runtime/scripts/hfs/rules/monorepo.mjs +9 -11
- package/runtime/scripts/hfs/rules/pipeline.mjs +13 -9
- package/runtime/scripts/hfs/rules/read.d.mts +5 -0
- package/runtime/scripts/hfs/rules/read.mjs +13 -0
- package/runtime/scripts/hfs/rules/secrets.d.mts +2 -0
- package/runtime/scripts/hfs/rules/secrets.mjs +4 -1
- package/runtime/scripts/hfs/rules/stacks-release.mjs +35 -21
- package/runtime/scripts/lib/worktree-exclude.mjs +1 -1
- package/scaffold/app.mjs +3 -3
- package/src/check-cache-structure-support.d.mts +52 -0
- package/src/main.d.mts +3 -2
- package/src/main.mjs +123 -109
- package/sync/ci-commands.d.mts +2 -1
- package/sync/ci-commands.mjs +19 -20
- package/sync/index.mjs +2 -2
- package/templates/app/package-scripts/core.json +2 -2
- package/templates/app/task-graph/turbo.json +0 -3
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// pipeline.mjs - HFS_CI_MISSING_CANON (R13): CI runs the pinned `starci app
|
|
2
|
-
// .github/workflows/ci.yml a `run:` step that runs the
|
|
3
|
-
// `npx starci-harness[@<version>] app lint`, or `npm run <script>` whose command runs
|
|
4
|
-
// (`npm run lint`, arguments after `--` allowed);
|
|
1
|
+
// pipeline.mjs - HFS_CI_MISSING_CANON (R13): CI runs the pinned lint gate of `starci app check`.
|
|
2
|
+
// .github/workflows/ci.yml a `run:` step that runs the lint gate: `starci app check --gate lint`,
|
|
3
|
+
// `npx starci-harness[@<version>] app check --gate lint`, or `npm run <script>` whose command runs it
|
|
4
|
+
// (`npm run lint`, arguments after `--` allowed);
|
|
5
|
+
// a version named in the step is the starci-harness pin,
|
|
5
6
|
// the version of the CLI that runs the check (canon-pins.mjs; an installed one is pinned by the pin check, R15)
|
|
6
7
|
// (the push hook is no lint or typecheck step: it checks the release gate, scripts/hfs/runtime-rules/hook-shape.mjs keeps its shape)
|
|
7
8
|
// A missing file is the slot manifest's finding (HFS_SLOT_REQUIRED_MISSING); a file that is present and lacks the step is this rule's.
|
|
@@ -13,7 +14,8 @@ const CI_FILE = '.github/workflows/ci.yml';
|
|
|
13
14
|
const hfsPackage = 'starci-harness';
|
|
14
15
|
const RUN_PREFIX = /^\s*(?:-\s+)?run:/;
|
|
15
16
|
const NPX_PREFIX = /^npx\s+(?:(?:--no-install|-y)\s+)?/;
|
|
16
|
-
const LINT_COMMAND = /^(?:starci-harness(?:@(\S+))?|starci)\s+app\s+
|
|
17
|
+
const LINT_COMMAND = /^(?:starci-harness(?:@(\S+))?|starci)\s+app\s+check/;
|
|
18
|
+
const GATE_ARGUMENT = /(?:^|\s)--gate(?:=|\s+)(\S+)/g;
|
|
17
19
|
const NPM_RUN_PREFIX = 'npm run ';
|
|
18
20
|
const EXAMPLE_RUN_PREFIX = 'node node_modules/@starci/hfs/runtime/scripts/hfs/example-secrets.mjs --run . -- ';
|
|
19
21
|
const SCRIPT_NAME = /^[\w:.-]+/;
|
|
@@ -56,7 +58,9 @@ function parseNpmRun(command) {
|
|
|
56
58
|
const args = separator ? tail.slice(separator[0].length) : null;
|
|
57
59
|
return args !== null && !LINE_TERMINATOR.test(args) ? { script, args } : null;
|
|
58
60
|
}
|
|
59
|
-
/**
|
|
61
|
+
/** Whether the arguments of an `app check` select the lint gate: `--gate lint` (or `--gate=lint`) among them. */
|
|
62
|
+
const runsLintGate = (tail) => [...tail.matchAll(GATE_ARGUMENT)].some((gate) => gate[1] === 'lint');
|
|
63
|
+
/** { version } of a `starci app check` command that runs the lint gate (`version` undefined when no starci-harness version is named), else null. */
|
|
60
64
|
function lintCommandOf(command) {
|
|
61
65
|
const rest = command.replace(NPX_PREFIX, '');
|
|
62
66
|
const match = LINT_COMMAND.exec(rest);
|
|
@@ -65,7 +69,7 @@ function lintCommandOf(command) {
|
|
|
65
69
|
const tail = rest.slice(match[0].length);
|
|
66
70
|
if (tail && !(WHITESPACE.test(tail[0]) && !LINE_TERMINATOR.test(tail.slice(1))))
|
|
67
71
|
return null;
|
|
68
|
-
return { version: match[1] };
|
|
72
|
+
return runsLintGate(tail) ? { version: match[1] } : null;
|
|
69
73
|
}
|
|
70
74
|
/** `npm run <script> -- <args>` spelled out as the root package.json script it runs; any other command is itself. */
|
|
71
75
|
function expanded(command, scripts) {
|
|
@@ -87,9 +91,9 @@ export function pipelineFindings({ repoRoot, files, pins }) {
|
|
|
87
91
|
const selected = isRuntimeExampleApp(repoRoot) ? commands.map(exampleCommandOf) : commands;
|
|
88
92
|
const lints = selected.flatMap((command) => chainedCommands(expanded(command, scripts))).map(lintCommandOf).filter(Boolean);
|
|
89
93
|
if (!lints.length)
|
|
90
|
-
findings.push(found(CI_MISSING_CANON, CI_FILE, `${CI_FILE} has no step that runs \`starci app lint\` (directly or through \`npm run lint\`); CI runs the
|
|
94
|
+
findings.push(found(CI_MISSING_CANON, CI_FILE, `${CI_FILE} has no step that runs \`starci app check --gate lint\` (directly or through \`npm run lint\`); CI runs the lint gate of the one app check`, { step: 'starci app check --gate lint' }));
|
|
91
95
|
else if (pinned && !lints.some((match) => match.version === undefined || match.version === pinned)) {
|
|
92
|
-
findings.push(found(CI_MISSING_CANON, CI_FILE, `${CI_FILE} runs starci app
|
|
96
|
+
findings.push(found(CI_MISSING_CANON, CI_FILE, `${CI_FILE} runs starci app check at ${lints[0].version}, but ${hfsPackage} is pinned at ${pinned}; run the pinned version`, { step: 'starci app check --gate lint', pinned }));
|
|
93
97
|
}
|
|
94
98
|
}
|
|
95
99
|
return findings;
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { Finding, Parsed } from './rule-types.mjs';
|
|
2
|
+
/** Who is told which file a rule reads: the result cache records the read set of a cross-file rule through it. */
|
|
3
|
+
type ReadSink = (repoRoot: string, rel: string) => void;
|
|
4
|
+
/** Runs `run` with every `readText` / `readJson` it makes reported to `onRead` (before the read), and returns its value. */
|
|
5
|
+
export declare function recordReads<T>(onRead: ReadSink, run: () => T): T;
|
|
2
6
|
/** The text of `rel` under `repoRoot`, or null when it is absent, unreadable, over MAX_READ_BYTES or binary. */
|
|
3
7
|
export declare function readText(repoRoot: string, rel: string): string | null;
|
|
4
8
|
/** The JSON of `rel`, or null when it is absent or not JSON. */
|
|
5
9
|
export declare function readJson(repoRoot: string, rel: string): Parsed | null;
|
|
6
10
|
/** One error finding of `code` on `file`; `extra` carries the finding's own fields. */
|
|
7
11
|
export declare const found: (code: string, file: string, message: string, extra?: Record<string, unknown>) => Finding;
|
|
12
|
+
export {};
|
|
@@ -3,8 +3,21 @@ import fs from 'node:fs';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
/** Files above this size are not read by a tree check (a tracked binary or a generated dump is no source of these laws). */
|
|
5
5
|
const MAX_READ_BYTES = 1024 * 1024;
|
|
6
|
+
let sink = null;
|
|
7
|
+
/** Runs `run` with every `readText` / `readJson` it makes reported to `onRead` (before the read), and returns its value. */
|
|
8
|
+
export function recordReads(onRead, run) {
|
|
9
|
+
const before = sink;
|
|
10
|
+
sink = onRead;
|
|
11
|
+
try {
|
|
12
|
+
return run();
|
|
13
|
+
}
|
|
14
|
+
finally {
|
|
15
|
+
sink = before;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
6
18
|
/** The text of `rel` under `repoRoot`, or null when it is absent, unreadable, over MAX_READ_BYTES or binary. */
|
|
7
19
|
export function readText(repoRoot, rel) {
|
|
20
|
+
sink?.(repoRoot, rel);
|
|
8
21
|
try {
|
|
9
22
|
const target = path.join(repoRoot, rel);
|
|
10
23
|
if (fs.statSync(target).size > MAX_READ_BYTES)
|
|
@@ -17,6 +17,8 @@ export declare function secretFileFindings({ file, text, manifestText }: {
|
|
|
17
17
|
text: string | null;
|
|
18
18
|
manifestText?: string | null | undefined;
|
|
19
19
|
}): import("./rule-types.mjs").Finding[];
|
|
20
|
+
/** The application-stacks manifest beside a README (the one other file a README's secret findings read), in the README's own scope; null for any other file. */
|
|
21
|
+
export declare const readmeManifestOf: (file: string) => string[] | null;
|
|
20
22
|
/** The findings of R06 over `files` (tracked paths) of `repoRoot`; `resolver` names the slots. */
|
|
21
23
|
export declare function secretFindings({ repoRoot, files, resolver }: {
|
|
22
24
|
repoRoot: string;
|
|
@@ -51,6 +51,8 @@ export function secretFileFindings({ file, text, manifestText }) {
|
|
|
51
51
|
}
|
|
52
52
|
return findings;
|
|
53
53
|
}
|
|
54
|
+
/** The application-stacks manifest beside a README (the one other file a README's secret findings read), in the README's own scope; null for any other file. */
|
|
55
|
+
export const readmeManifestOf = (file) => (/(?:^|\/)README\.md$/.test(file) ? [path.posix.join(path.posix.dirname(file), '.starcistacks/application-stacks.yaml')] : null);
|
|
54
56
|
/** The findings of R06 over `files` (tracked paths) of `repoRoot`; `resolver` names the slots. */
|
|
55
57
|
export function secretFindings({ repoRoot, files, resolver }) {
|
|
56
58
|
const findings = [];
|
|
@@ -61,7 +63,8 @@ export function secretFindings({ repoRoot, files, resolver }) {
|
|
|
61
63
|
findings.push(found(PLAINTEXT_SECRET, file, `${file} is a plaintext secret file tracked in ${slot.slot}${destination}`, { slot: slot.slot, goesTo: slot.goesTo }));
|
|
62
64
|
continue;
|
|
63
65
|
}
|
|
64
|
-
const
|
|
66
|
+
const manifestPath = readmeManifestOf(file)?.[0];
|
|
67
|
+
const manifestText = manifestPath === undefined ? undefined : readText(repoRoot, manifestPath);
|
|
65
68
|
findings.push(...secretFileFindings({ file, text: readText(repoRoot, file), manifestText }));
|
|
66
69
|
}
|
|
67
70
|
return findings;
|
|
@@ -6,6 +6,7 @@ import { loadTypescript } from '../../lib/package-at.mjs';
|
|
|
6
6
|
import { walkSqlAst, sqlParser, sqlCallArgument } from '../../lib/sql-ast.mjs';
|
|
7
7
|
import { isPlainObject as plain } from '../../../engine/plain-object.mjs';
|
|
8
8
|
import { found, readText } from './read.mjs';
|
|
9
|
+
import { memoUnit } from '../rule-memo.mjs';
|
|
9
10
|
import { stacksDeploymentContexts } from './stacks-runtime.mjs';
|
|
10
11
|
// PostgreSQL initializes asynchronously, while HFS finders must stay synchronous.
|
|
11
12
|
// Optional loading also lets dependency-free relocated development gates import this rule.
|
|
@@ -39,20 +40,26 @@ function safeRelative(relative) {
|
|
|
39
40
|
return false;
|
|
40
41
|
return relative.split('/').every((part) => part && part !== '.' && part !== '..');
|
|
41
42
|
}
|
|
42
|
-
|
|
43
|
+
/** `links` remembers which directories are symbolic links for the duration of one scan, so a directory shared by many files is asked once. */
|
|
44
|
+
function publicReference(repoRoot, files, relative, links = new Map()) {
|
|
43
45
|
if (!repoRoot || !safeRelative(relative))
|
|
44
46
|
return false;
|
|
45
|
-
if (!files.includes(relative))
|
|
47
|
+
if (!(files instanceof Set ? files.has(relative) : files.includes(relative)))
|
|
46
48
|
return false;
|
|
47
49
|
const root = path.resolve(repoRoot), target = path.resolve(root, relative);
|
|
48
50
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
const stat = fs.lstatSync(target);
|
|
52
|
+
if (stat.isSymbolicLink())
|
|
53
|
+
return false;
|
|
54
|
+
for (let cursor = path.dirname(target); cursor !== root; cursor = path.dirname(cursor)) {
|
|
55
|
+
let link = links.get(cursor);
|
|
56
|
+
if (link === undefined) {
|
|
57
|
+
link = fs.lstatSync(cursor).isSymbolicLink();
|
|
58
|
+
links.set(cursor, link);
|
|
59
|
+
}
|
|
60
|
+
if (link)
|
|
52
61
|
return false;
|
|
53
|
-
cursor = path.dirname(cursor);
|
|
54
62
|
}
|
|
55
|
-
const stat = fs.lstatSync(target);
|
|
56
63
|
return stat.isFile() && stat.size > 0 && stat.size <= MAX_BYTES;
|
|
57
64
|
}
|
|
58
65
|
catch {
|
|
@@ -356,30 +363,37 @@ function templateObservations(node, file) {
|
|
|
356
363
|
return [{ id: null, cadence: null, at: file }];
|
|
357
364
|
return sqlObservations(named, file);
|
|
358
365
|
}
|
|
359
|
-
function
|
|
360
|
-
const sql = file.startsWith('db/migrations/') && file.endsWith('.sql') && !file.startsWith('db/migrations/archive/');
|
|
361
|
-
const sourceFile = SOURCE.test(file) && !TEST.test(file);
|
|
362
|
-
if ((!sql && !sourceFile) || !publicReference(repoRoot, files, file))
|
|
363
|
-
return [];
|
|
366
|
+
function observationsOf({ doc, repoRoot, file, sql }) {
|
|
364
367
|
const text = readText(repoRoot, file);
|
|
365
368
|
if (text === null)
|
|
366
|
-
return [];
|
|
369
|
+
return { observations: [], issues: [] };
|
|
367
370
|
if (sql)
|
|
368
|
-
return sqlObservations(text, file);
|
|
371
|
+
return { observations: sqlObservations(text, file), issues: [] };
|
|
369
372
|
ts ??= loadTypescript(repoRoot, HERE);
|
|
370
|
-
if (!ts)
|
|
371
|
-
issues
|
|
372
|
-
return [];
|
|
373
|
-
}
|
|
373
|
+
if (!ts)
|
|
374
|
+
return { observations: [], issues: [scheduleIssue(file, 'TypeScript is required to inspect recurring application work')] };
|
|
374
375
|
const source = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true);
|
|
375
|
-
issues
|
|
376
|
-
return sourceSqlObservations(source, file);
|
|
376
|
+
const issues = sourceObservationIssues(doc, file, source, sourceBindings(source));
|
|
377
|
+
return { observations: sourceSqlObservations(source, file), issues };
|
|
378
|
+
}
|
|
379
|
+
function recurringObservations({ doc, repoRoot, files, file, issues, links, tools }) {
|
|
380
|
+
const sql = file.startsWith('db/migrations/') && file.endsWith('.sql') && !file.startsWith('db/migrations/archive/');
|
|
381
|
+
const sourceFile = SOURCE.test(file) && !TEST.test(file);
|
|
382
|
+
if ((!sql && !sourceFile) || !publicReference(repoRoot, files, file, links))
|
|
383
|
+
return [];
|
|
384
|
+
// The result cache keeps this per file: the bytes of the file, the manifest and the parsers decide it (contexts/project/check-cache.md).
|
|
385
|
+
const one = memoUnit(repoRoot, 'recurring-observations', file, `${tools}
|
|
386
|
+
${JSON.stringify(doc)}`, { observations: [], issues: [] }, () => observationsOf({ doc, repoRoot, file, sql }));
|
|
387
|
+
issues.push(...one.issues);
|
|
388
|
+
return one.observations;
|
|
377
389
|
}
|
|
378
390
|
function recurringSourceIssues({ doc, repoRoot, files }) {
|
|
379
391
|
const issues = [], cron = new Map();
|
|
380
392
|
const paths = [...new Set(files)].sort((left, right) => left.localeCompare(right));
|
|
393
|
+
const tracked = new Set(paths), links = new Map();
|
|
394
|
+
const tools = `typescript=${loadTypescript(repoRoot, HERE) !== null} sql=${pg !== null}`;
|
|
381
395
|
for (const file of paths) {
|
|
382
|
-
const observations = recurringObservations({ doc, repoRoot, files, file, issues });
|
|
396
|
+
const observations = recurringObservations({ doc, repoRoot, files: tracked, file, issues, links, tools });
|
|
383
397
|
for (const observation of observations) {
|
|
384
398
|
if ('remove' in observation) {
|
|
385
399
|
if (observation.remove)
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// <repo>/.starciwork/worktrees/<name> one runtime scratch tree
|
|
6
6
|
// The directory is git-excluded (.git/info/exclude), so `git status`,
|
|
7
7
|
// `git ls-files --others --exclude-standard` and sonar never see it; every scanner that walks the filesystem
|
|
8
|
-
// (canon-scan's eslint, starci app
|
|
8
|
+
// (canon-scan's eslint, starci app check, the architecture walkers, input digests, proof-integrity, Work readers)
|
|
9
9
|
// skips it with isWorktreesPath / WORKTREES_IGNORE_GLOBS, or it would lint and hash every sibling worktree.
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { posixPath } from './path-key.mjs';
|
package/scaffold/app.mjs
CHANGED
|
@@ -21,7 +21,7 @@ import { HARNESS_PACKAGE, canonPins } from '../runtime/scripts/hfs/canon-pins.mj
|
|
|
21
21
|
import { TEMPLATES_DIR, appSource, imageFiles, render, renderTargets, writeTargets } from '../sync/index.mjs';
|
|
22
22
|
import { ScaffoldError } from './service.mjs';
|
|
23
23
|
import { testWorldFiles } from './test-world.mjs';
|
|
24
|
-
import { FE_APP_SCRIPTS, PACKAGE_MANAGER, WORKSPACES,
|
|
24
|
+
import { FE_APP_SCRIPTS, PACKAGE_MANAGER, WORKSPACES, feAppPackageName } from '../runtime/scripts/hfs/rules/monorepo.mjs';
|
|
25
25
|
import { isLinkLike } from '../runtime/scripts/api/fs/is-link-like.mjs';
|
|
26
26
|
import { unlinkOnly } from '../runtime/scripts/api/fs/lib.mjs';
|
|
27
27
|
import { initializeAppIdentity } from '../runtime/scripts/hfs/secret-init.mjs';
|
|
@@ -200,8 +200,8 @@ const FE_PACKAGES = Object.freeze({
|
|
|
200
200
|
ui: { exports: ['.'], dependencies: { '@heroui/react': null, '@starci/grammar': null, '@starci/heroicons': null, '@xyflow/react': null, 'react-aria-components': null, 'next-intl': null, react: null, 'react-dom': null } },
|
|
201
201
|
i18n: { exports: ['.', './proxy', './routing'], dependencies: { next: null, 'next-intl': null, 'server-only': null } },
|
|
202
202
|
});
|
|
203
|
-
/** The scripts of every fe package workspace: tsc builds it to dist and type-checks it
|
|
204
|
-
const PACKAGE_WORKSPACE_SCRIPTS = Object.freeze({ build: 'tsc -p tsconfig.build.json',
|
|
203
|
+
/** The scripts of every fe package workspace: tsc builds it to dist and type-checks it. */
|
|
204
|
+
const PACKAGE_WORKSPACE_SCRIPTS = Object.freeze({ build: 'tsc -p tsconfig.build.json', typecheck: 'tsc --noEmit -p tsconfig.json' });
|
|
205
205
|
/** The app hfs.json of a new app called `name`; full remains the byte-for-byte default declaration. */
|
|
206
206
|
export const starterDeclaration = (name, manifest = loadSlotManifest(), edition = 'full') => ({
|
|
207
207
|
hfs: manifest.major,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TestContext } from 'node:test';
|
|
2
|
+
import { type PassResult } from '../cache/hfs-family.mjs';
|
|
3
|
+
import type { CacheRecord } from '../cache/types.mjs';
|
|
4
|
+
import type { MachineFn } from '../runtime/scripts/hfs/check.mjs';
|
|
5
|
+
export declare const PORTABLE: {
|
|
6
|
+
parts: {
|
|
7
|
+
name: string;
|
|
8
|
+
sha256: string;
|
|
9
|
+
}[];
|
|
10
|
+
portable: boolean;
|
|
11
|
+
localReasons: never[];
|
|
12
|
+
};
|
|
13
|
+
/** An AWS access key shape the secret scan refuses (built from parts: the fixture is data, not a credential). */
|
|
14
|
+
export declare const FAKE_KEY: string;
|
|
15
|
+
/** The files of the fixture app; the untracked ones (generated code) are written after the commit. */
|
|
16
|
+
export declare const FILES: Readonly<Record<string, string>>;
|
|
17
|
+
/** The untracked, git-ignored generated code the fe program includes. */
|
|
18
|
+
export declare const GENERATED = "fe/src/__generated__/graphql.ts";
|
|
19
|
+
export declare function write(root: string, file: string, text: string): void;
|
|
20
|
+
/** A temp git repository holding `files` (committed) and the generated input (not tracked). */
|
|
21
|
+
export declare function appRepo(t: TestContext, files?: Readonly<Record<string, string>>): string;
|
|
22
|
+
/** The architecture machine as a double that counts its runs per side (`calls`) and can act while it runs (`during`). */
|
|
23
|
+
export declare function machineDouble(during?: (side: string) => void): {
|
|
24
|
+
machine: MachineFn;
|
|
25
|
+
calls: string[];
|
|
26
|
+
};
|
|
27
|
+
export interface RunOptions {
|
|
28
|
+
readonly hfs?: boolean;
|
|
29
|
+
readonly secrets?: boolean;
|
|
30
|
+
readonly during?: (side: string) => void;
|
|
31
|
+
readonly fingerprint?: typeof PORTABLE;
|
|
32
|
+
readonly tracked?: readonly string[];
|
|
33
|
+
}
|
|
34
|
+
/** One structure pass over `root` against the committed cache of the work tree; the session is flushed. */
|
|
35
|
+
export declare function runPass(root: string, options?: RunOptions): Promise<{
|
|
36
|
+
pass: PassResult;
|
|
37
|
+
calls: string[];
|
|
38
|
+
hfs: readonly CacheRecord[];
|
|
39
|
+
secrets: readonly CacheRecord[];
|
|
40
|
+
}>;
|
|
41
|
+
/** `path\0step` to hash, for comparing which records a run rewrote. */
|
|
42
|
+
export declare const hashes: (records: readonly CacheRecord[]) => Map<string, string>;
|
|
43
|
+
/** The record keys whose hash differs between two runs, added or removed ones included. */
|
|
44
|
+
export declare function changed(before: readonly CacheRecord[], after: readonly CacheRecord[]): string[];
|
|
45
|
+
/** Every file-system access `run` makes under `root` (outside node_modules, which the lockfile in the fingerprint stands for), as `method path`. */
|
|
46
|
+
export declare function recordFs<T>(root: string, run: () => T): {
|
|
47
|
+
value: T;
|
|
48
|
+
accessed: Set<string>;
|
|
49
|
+
};
|
|
50
|
+
export declare const filesOf: (accessed: Set<string>) => Set<string>;
|
|
51
|
+
/** The fixture of the per-file rules: a secret, a bypass marker, a long source, a README beside a stack manifest and files that are clean. */
|
|
52
|
+
export declare const RULE_FILES: Readonly<Record<string, string>>;
|
package/src/main.d.mts
CHANGED
|
@@ -49,10 +49,11 @@ interface AppIo {
|
|
|
49
49
|
type MainOptions = {
|
|
50
50
|
[K in keyof AppIo]?: AppIo[K] | undefined;
|
|
51
51
|
};
|
|
52
|
+
/** An argument the parser refuses (an unknown flag, a missing value, a form a verb does not take): the one error `main` follows with the usage. */
|
|
53
|
+
export declare class UsageError extends Error {
|
|
54
|
+
}
|
|
52
55
|
export declare const VERBS: readonly string[];
|
|
53
56
|
export declare const USAGE: string;
|
|
54
|
-
/** The nearest folder at or above `dir` that holds hfs.json (the app root of a workspace), or `dir` itself when none does (the lint then refuses it). */
|
|
55
|
-
export declare function appRootAbove(dir: string): string;
|
|
56
57
|
/**
|
|
57
58
|
* `harness` ({ version }) is the CLI that runs this package in-process: its version is the starci-harness an app is judged and
|
|
58
59
|
* scaffolded at (runtime/scripts/hfs/canon-pins.mjs). `run` injects the database runner, `runLinter` the linter transport, `checkApp`
|