@clearlydev/nx-archweave 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +265 -2
- package/dist/bin/review-verdict.js +330 -0
- package/dist/eslint/fsd-boundaries.d.ts +119 -0
- package/dist/eslint/fsd-boundaries.js +144 -0
- package/dist/eslint/index.d.ts +78 -0
- package/dist/eslint/index.js +256 -0
- package/dist/eslint/tsconfig-aliases.d.ts +18 -0
- package/dist/eslint/tsconfig-aliases.js +114 -0
- package/dist/generators/add-agent-rules/generator.js +550 -0
- package/dist/generators/add-agent-rules/render.js +46 -0
- package/dist/generators/add-backend/generator.js +784 -0
- package/dist/generators/add-backend/render.js +102 -0
- package/dist/generators/add-fsd-lite/generator.js +553 -0
- package/dist/generators/add-fsd-lite/render.js +72 -0
- package/dist/generators/add-github-templates/files/en/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/en/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/en/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/files/ko/codeowners/.github/CODEOWNERS.template +4 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/bug-report.yml.template +28 -0
- package/dist/generators/add-github-templates/files/ko/issue/.github/ISSUE_TEMPLATE/feature-request.yml.template +17 -0
- package/dist/generators/add-github-templates/files/ko/pr/.github/PULL_REQUEST_TEMPLATE.md.template +12 -0
- package/dist/generators/add-github-templates/generator.js +125 -0
- package/dist/generators/add-github-templates/render.js +26 -0
- package/dist/generators/add-integration/generator.js +849 -0
- package/dist/generators/add-integration/render.js +34 -0
- package/dist/generators/add-review/generator.js +921 -0
- package/dist/generators/add-review/render.js +96 -0
- package/dist/generators/allow-builds/generator.js +436 -0
- package/dist/generators/allow-builds/render.js +82 -0
- package/dist/generators/doctor/generator.js +2462 -0
- package/dist/generators/doctor/integration-report.js +70 -0
- package/dist/generators/doctor/render.js +10 -0
- package/dist/generators/entity/generator.js +395 -0
- package/dist/generators/feature/generator.js +395 -0
- package/dist/generators/init/generator.js +3211 -0
- package/dist/generators/init/render.js +125 -0
- package/dist/generators/init/validation.js +74 -0
- package/dist/generators/install-integration/generator.js +1265 -0
- package/dist/generators/install-integration/render.js +128 -0
- package/dist/generators/page/generator.js +395 -0
- package/dist/generators/record-integration/generator.js +883 -0
- package/dist/generators/record-integration/render.js +31 -0
- package/dist/generators/shared/dry-run.js +12 -0
- package/dist/generators/shared/frontend-app.js +57 -0
- package/dist/generators/shared/generator.js +395 -0
- package/dist/generators/shared/project-type.js +39 -0
- package/dist/generators/shared/record-pipeline.js +34 -0
- package/dist/generators/shared/run-nx.js +24 -0
- package/dist/generators/shared/template-files.js +38 -0
- package/dist/generators/shared/toolkit-config-file.js +110 -0
- package/dist/generators/slice/core.js +185 -0
- package/dist/generators/slice/files/index/index.ts.template +14 -0
- package/dist/generators/slice/files/module/__name__.spec.ts.template +11 -0
- package/dist/generators/slice/files/module/__name__.ts.template +11 -0
- package/dist/generators/slice/files/segment/index.ts.template +20 -0
- package/dist/generators/slice/render.js +70 -0
- package/dist/generators/widget/generator.js +395 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +711 -0
- package/dist/lib/nx-plugin.d.ts +2 -0
- package/dist/lib/nx-plugin.js +7 -0
- package/dist/runtime/integrations-composition-root.js +20 -0
- package/dist/runtime/node-command-runner.js +81 -0
- package/generators.json +85 -0
- package/package.json +96 -4
- package/src/generators/add-agent-rules/schema.json +43 -0
- package/src/generators/add-backend/schema.json +39 -0
- package/src/generators/add-fsd-lite/schema.json +43 -0
- package/src/generators/add-github-templates/schema.json +35 -0
- package/src/generators/add-integration/schema.json +35 -0
- package/src/generators/add-review/schema.json +72 -0
- package/src/generators/allow-builds/schema.json +20 -0
- package/src/generators/doctor/schema.json +20 -0
- package/src/generators/entity/schema.json +37 -0
- package/src/generators/feature/schema.json +37 -0
- package/src/generators/init/schema.json +98 -0
- package/src/generators/install-integration/schema.json +35 -0
- package/src/generators/page/schema.json +53 -0
- package/src/generators/record-integration/schema.json +26 -0
- package/src/generators/shared/schema.json +32 -0
- package/src/generators/widget/schema.json +45 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/** Line-level LCS diff — small generated files, no diff library. */
|
|
2
|
+
export function renderLineDiff(existing, generated) {
|
|
3
|
+
const a = existing.split('\n');
|
|
4
|
+
const b = generated.split('\n');
|
|
5
|
+
const m = a.length;
|
|
6
|
+
const n = b.length;
|
|
7
|
+
const lcs = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
8
|
+
for (let i = m - 1; i >= 0; i--) {
|
|
9
|
+
for (let j = n - 1; j >= 0; j--) {
|
|
10
|
+
lcs[i][j] = a[i] === b[j] ? lcs[i + 1][j + 1] + 1 : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const out = ['--- existing', '+++ generated'];
|
|
14
|
+
let i = 0;
|
|
15
|
+
let j = 0;
|
|
16
|
+
while (i < m && j < n) {
|
|
17
|
+
if (a[i] === b[j]) {
|
|
18
|
+
out.push(` ${a[i]}`);
|
|
19
|
+
i++;
|
|
20
|
+
j++;
|
|
21
|
+
}
|
|
22
|
+
else if (lcs[i + 1][j] >= lcs[i][j + 1]) {
|
|
23
|
+
out.push(`- ${a[i]}`);
|
|
24
|
+
i++;
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
out.push(`+ ${b[j]}`);
|
|
28
|
+
j++;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
while (i < m)
|
|
32
|
+
out.push(`- ${a[i++]}`);
|
|
33
|
+
while (j < n)
|
|
34
|
+
out.push(`+ ${b[j++]}`);
|
|
35
|
+
return out.join('\n');
|
|
36
|
+
}
|
|
37
|
+
const WORKFLOW_ACTION_LINES = {
|
|
38
|
+
created: 'created',
|
|
39
|
+
overwritten: 'overwritten with the generated version (per --onConflict=overwrite)',
|
|
40
|
+
unchanged: 'unchanged (already matches the generated version)',
|
|
41
|
+
proposed: 'NOT written — differs from the generated version (merge proposal below)',
|
|
42
|
+
skipped: 'left as-is (per --onConflict=skip)',
|
|
43
|
+
alternate: 'generated alongside the existing file',
|
|
44
|
+
aborted: 'NOT written',
|
|
45
|
+
};
|
|
46
|
+
const CONFIG_ACTION_LINES = {
|
|
47
|
+
created: 'created with the review field',
|
|
48
|
+
updated: 'review field updated',
|
|
49
|
+
unchanged: 'review field unchanged',
|
|
50
|
+
'not-updated': 'NOT updated — kept consistent with the workflow file that is actually in effect',
|
|
51
|
+
aborted: 'NOT updated',
|
|
52
|
+
};
|
|
53
|
+
export function renderAddReviewReport(report) {
|
|
54
|
+
const lines = [];
|
|
55
|
+
if (report.packageManagerNote !== undefined) {
|
|
56
|
+
lines.push(`Note: ${report.packageManagerNote}`);
|
|
57
|
+
}
|
|
58
|
+
if (report.workflowAction === 'aborted') {
|
|
59
|
+
lines.push('add-review aborted — nothing was written.');
|
|
60
|
+
lines.push(` toolkit.config.json: ${report.configError ?? 'unusable'}`);
|
|
61
|
+
lines.push(' Fix the config file manually, then rerun the generator.');
|
|
62
|
+
return lines.join('\n');
|
|
63
|
+
}
|
|
64
|
+
if (report.dryRun) {
|
|
65
|
+
lines.push('add-review dry-run — no files were written.');
|
|
66
|
+
lines.push('');
|
|
67
|
+
lines.push('Generated workflow YAML:');
|
|
68
|
+
lines.push(report.workflowYaml ?? '');
|
|
69
|
+
lines.push('');
|
|
70
|
+
lines.push('toolkit.config.json review field:');
|
|
71
|
+
lines.push(report.configSnippetJson ?? '');
|
|
72
|
+
lines.push('');
|
|
73
|
+
lines.push('Effective decision:');
|
|
74
|
+
}
|
|
75
|
+
lines.push(` ${report.workflowPath}: ${WORKFLOW_ACTION_LINES[report.workflowAction]}`);
|
|
76
|
+
lines.push(` toolkit.config.json: ${CONFIG_ACTION_LINES[report.configAction]}`);
|
|
77
|
+
if (report.workflowAction === 'proposed' && report.diff) {
|
|
78
|
+
lines.push('');
|
|
79
|
+
lines.push(report.diff);
|
|
80
|
+
lines.push('');
|
|
81
|
+
lines.push('To resolve, rerun with one of:');
|
|
82
|
+
lines.push(' --onConflict=overwrite replace the existing file with the generated version');
|
|
83
|
+
lines.push(' --onConflict=skip keep the existing file');
|
|
84
|
+
lines.push(' --onConflict=alternate-filename write the generated version alongside it');
|
|
85
|
+
}
|
|
86
|
+
if (report.workflowAction === 'alternate') {
|
|
87
|
+
lines.push('');
|
|
88
|
+
lines.push(' Review the generated file, then replace the main workflow file with it yourself;');
|
|
89
|
+
lines.push(' the config stays pointing at the workflow that is actually in effect until you do.');
|
|
90
|
+
}
|
|
91
|
+
lines.push('');
|
|
92
|
+
lines.push(`Note: the review workflow stays green-skipped until you register the ${report.requiredSecretName} secret.`);
|
|
93
|
+
lines.push('Note: this workflow runs with elevated permissions only after it is merged to the default branch —');
|
|
94
|
+
lines.push('merging is the first real validation.');
|
|
95
|
+
return lines.join('\n');
|
|
96
|
+
}
|
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
// src/generators/allow-builds/generator.ts
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
// ../shared/dist/lib/spawnable-command.js
|
|
5
|
+
import { statSync } from "node:fs";
|
|
6
|
+
import { win32 } from "node:path";
|
|
7
|
+
function isFile(path) {
|
|
8
|
+
try {
|
|
9
|
+
return statSync(path).isFile();
|
|
10
|
+
} catch {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
var DEFAULT_ENV = { platform: process.platform, env: process.env, isFile };
|
|
15
|
+
function envValue(env, name) {
|
|
16
|
+
const key = Object.keys(env).find((candidate) => candidate.toUpperCase() === name);
|
|
17
|
+
return key === void 0 ? void 0 : env[key];
|
|
18
|
+
}
|
|
19
|
+
function resolveWindowsCommand(command, environment) {
|
|
20
|
+
const exts = (envValue(environment.env, "PATHEXT") ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean);
|
|
21
|
+
const candidates = (base) => win32.extname(base) !== "" ? [base] : exts.map((ext) => base + ext);
|
|
22
|
+
if (win32.isAbsolute(command) || /[\\/]/.test(command)) {
|
|
23
|
+
return candidates(command).find(environment.isFile);
|
|
24
|
+
}
|
|
25
|
+
const dirs = (envValue(environment.env, "PATH") ?? "").split(";").filter(Boolean);
|
|
26
|
+
for (const dir of dirs) {
|
|
27
|
+
const found = candidates(win32.join(dir, command)).find(environment.isFile);
|
|
28
|
+
if (found !== void 0)
|
|
29
|
+
return found;
|
|
30
|
+
}
|
|
31
|
+
return void 0;
|
|
32
|
+
}
|
|
33
|
+
var META_CHARS = /([()\][%!^"`<>&|;, *?])/g;
|
|
34
|
+
var BIN_SHIM = /node_modules[\\/]\.bin[\\/][^\\/]+\.cmd$/i;
|
|
35
|
+
function escapeCmdArgument(arg, doubleEscape = false) {
|
|
36
|
+
let quoted = arg.replace(/(\\*)"/g, '$1$1\\"');
|
|
37
|
+
quoted = quoted.replace(/(\\*)$/, "$1$1");
|
|
38
|
+
const escaped = `"${quoted}"`.replace(META_CHARS, "^$1");
|
|
39
|
+
return doubleEscape ? escaped.replace(META_CHARS, "^$1") : escaped;
|
|
40
|
+
}
|
|
41
|
+
function spawnableCommand(command, args, environment = DEFAULT_ENV) {
|
|
42
|
+
if (environment.platform !== "win32")
|
|
43
|
+
return { command, args: [...args] };
|
|
44
|
+
const resolved = resolveWindowsCommand(command, environment);
|
|
45
|
+
if (resolved === void 0 || !/\.(cmd|bat)$/i.test(resolved))
|
|
46
|
+
return { command, args: [...args] };
|
|
47
|
+
const doubleEscape = BIN_SHIM.test(resolved);
|
|
48
|
+
const line = [resolved.replace(META_CHARS, "^$1"), ...args.map((arg) => escapeCmdArgument(arg, doubleEscape))].join(" ");
|
|
49
|
+
return {
|
|
50
|
+
command: envValue(environment.env, "COMSPEC") ?? "cmd.exe",
|
|
51
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
52
|
+
windowsVerbatimArguments: true
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ../template-engine/dist/environment/package-manager.js
|
|
57
|
+
var LOCKFILES = [
|
|
58
|
+
["yarn", ["yarn.lock"]],
|
|
59
|
+
["bun", ["bun.lock", "bun.lockb"]],
|
|
60
|
+
["npm", ["package-lock.json"]]
|
|
61
|
+
];
|
|
62
|
+
function detectWorkspacePackageManager(exists, read = () => void 0) {
|
|
63
|
+
if (exists("pnpm-lock.yaml") || exists("pnpm-workspace.yaml"))
|
|
64
|
+
return "pnpm";
|
|
65
|
+
const found = LOCKFILES.filter(([, files]) => files.some((file) => exists(file))).map(([pm]) => pm);
|
|
66
|
+
if (found.length <= 1)
|
|
67
|
+
return found[0] ?? null;
|
|
68
|
+
const declared = /"packageManager"\s*:\s*"(npm|yarn|bun)@/.exec(read("package.json") ?? "")?.[1];
|
|
69
|
+
const named = found.find((pm) => pm === declared);
|
|
70
|
+
if (named)
|
|
71
|
+
return named;
|
|
72
|
+
return found.includes("npm") ? "npm" : found[0];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ../template-engine/dist/pnpm/allow-builds.js
|
|
76
|
+
var PENDING_PLACEHOLDER = "set this to true or false";
|
|
77
|
+
function isPlainPackageName(name) {
|
|
78
|
+
return /^(?:@[a-z0-9][a-z0-9._~-]*\/)?[a-z0-9][a-z0-9._~-]*$/i.test(name);
|
|
79
|
+
}
|
|
80
|
+
function allowPackageBuilds(yamlText, names, options = {}) {
|
|
81
|
+
const bom = yamlText.startsWith("\uFEFF") ? "\uFEFF" : "";
|
|
82
|
+
const body = yamlText.slice(bom.length);
|
|
83
|
+
const eol = body.includes("\r\n") ? "\r\n" : "\n";
|
|
84
|
+
const unique = [...new Set(names)];
|
|
85
|
+
const listIsRead = options.pnpmMajor === void 0 || options.pnpmMajor < 11;
|
|
86
|
+
const result = edit(body.split(/\r?\n/), eol, unique, listIsRead);
|
|
87
|
+
return result.status === "updated" ? { ...result, text: `${bom}${result.text}` } : result;
|
|
88
|
+
}
|
|
89
|
+
function pnpmMajorFromModulesManifest(modulesYamlText) {
|
|
90
|
+
const match = /^\s*["']?packageManager["']?\s*:\s*["']?pnpm@(\d+)\./m.exec(modulesYamlText);
|
|
91
|
+
return match ? Number(match[1]) : void 0;
|
|
92
|
+
}
|
|
93
|
+
function edit(original, eol, names, listIsRead) {
|
|
94
|
+
const shape = checkDocumentShape(original);
|
|
95
|
+
if (shape !== void 0)
|
|
96
|
+
return { status: "unrecognized", reason: shape };
|
|
97
|
+
const lines = expandFlow(original, "allowBuilds");
|
|
98
|
+
if (typeof lines === "string")
|
|
99
|
+
return { status: "unrecognized", reason: lines };
|
|
100
|
+
const allowBuilds = findBlock(lines, "allowBuilds");
|
|
101
|
+
if (typeof allowBuilds === "string")
|
|
102
|
+
return { status: "unrecognized", reason: allowBuilds };
|
|
103
|
+
if (allowBuilds)
|
|
104
|
+
return editAllowBuilds(lines, eol, allowBuilds, names);
|
|
105
|
+
if (!listIsRead)
|
|
106
|
+
return appendAllowBuilds(original, eol, names);
|
|
107
|
+
const listLines = expandFlow(original, "onlyBuiltDependencies");
|
|
108
|
+
if (typeof listLines === "string")
|
|
109
|
+
return { status: "unrecognized", reason: listLines };
|
|
110
|
+
const onlyBuilt = findBlock(listLines, "onlyBuiltDependencies");
|
|
111
|
+
if (typeof onlyBuilt === "string")
|
|
112
|
+
return { status: "unrecognized", reason: onlyBuilt };
|
|
113
|
+
if (onlyBuilt)
|
|
114
|
+
return editOnlyBuilt(listLines, eol, onlyBuilt, names);
|
|
115
|
+
return appendAllowBuilds(original, eol, names);
|
|
116
|
+
}
|
|
117
|
+
function appendAllowBuilds(original, eol, names) {
|
|
118
|
+
const changes = names.map((name) => ({ name, previous: "absent" }));
|
|
119
|
+
const entries = names.map((name) => ` ${yamlPackageKey(name)}: true`);
|
|
120
|
+
const head = original[original.length - 1] === "" ? original.slice(0, -1) : original;
|
|
121
|
+
return { status: "updated", target: "allowBuilds", changes, text: [...head, "allowBuilds:", ...entries, ""].join(eol) };
|
|
122
|
+
}
|
|
123
|
+
function editAllowBuilds(lines, eol, block, names) {
|
|
124
|
+
const entries = [];
|
|
125
|
+
for (const index of block.entries) {
|
|
126
|
+
const match = /^(\s+)('([^']*)'|"([^"]*)"|([^\s'"#-]\S*?)):(?:\s+(.*))?$/.exec(lines[index]);
|
|
127
|
+
if (!match)
|
|
128
|
+
return { status: "unrecognized", reason: 'allowBuilds holds something other than "<package>: <value>" entries' };
|
|
129
|
+
entries.push({ index, indent: match[1], rawKey: match[2], key: match[3] ?? match[4] ?? match[5], ...splitValue(match[6] ?? "") });
|
|
130
|
+
}
|
|
131
|
+
const indent = entries[0]?.indent ?? " ";
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
if (entries.filter((entry) => entry.key === name).length > 1)
|
|
134
|
+
return { status: "unrecognized", reason: `allowBuilds lists ${name} more than once` };
|
|
135
|
+
const versioned = entries.find((entry) => entry.key.startsWith(`${name}@`) && classify(entry.value) !== "allowed");
|
|
136
|
+
if (versioned)
|
|
137
|
+
return { status: "unrecognized", reason: `allowBuilds has a version-specific entry for ${name} (${versioned.key}) that is not true` };
|
|
138
|
+
}
|
|
139
|
+
const next = [...lines];
|
|
140
|
+
const appended = [];
|
|
141
|
+
const changes = names.map((name) => {
|
|
142
|
+
const entry = entries.find((candidate) => candidate.key === name);
|
|
143
|
+
if (!entry) {
|
|
144
|
+
appended.push(`${indent}${yamlPackageKey(name)}: true`);
|
|
145
|
+
return { name, previous: "absent" };
|
|
146
|
+
}
|
|
147
|
+
const previous = classify(entry.value);
|
|
148
|
+
if (previous !== "allowed")
|
|
149
|
+
next[entry.index] = `${entry.indent}${entry.rawKey}: true${entry.comment ? ` ${entry.comment}` : ""}`;
|
|
150
|
+
return { name, previous };
|
|
151
|
+
});
|
|
152
|
+
if (changes.every((change) => change.previous === "allowed"))
|
|
153
|
+
return { status: "unchanged", target: "allowBuilds", changes };
|
|
154
|
+
next.splice(insertionPoint(block), 0, ...appended);
|
|
155
|
+
return { status: "updated", target: "allowBuilds", changes, text: next.join(eol) };
|
|
156
|
+
}
|
|
157
|
+
function editOnlyBuilt(lines, eol, block, names) {
|
|
158
|
+
const listed = /* @__PURE__ */ new Set();
|
|
159
|
+
for (const index of block.entries) {
|
|
160
|
+
const item = /^\s+-\s+(?:'([^']*)'|"([^"]*)"|([^\s#'"][^#]*?))\s*(?:#.*)?$/.exec(lines[index]);
|
|
161
|
+
const value = item ? item[1] ?? item[2] ?? item[3] : void 0;
|
|
162
|
+
if (value === void 0 || item?.[3] !== void 0 && /:(\s|$)/.test(value)) {
|
|
163
|
+
return { status: "unrecognized", reason: 'onlyBuiltDependencies holds something other than "- <package>" items' };
|
|
164
|
+
}
|
|
165
|
+
listed.add(value);
|
|
166
|
+
}
|
|
167
|
+
const indent = block.entries.length > 0 ? leadingSpace(lines[block.entries[0]]) : " ";
|
|
168
|
+
const changes = names.map((name) => ({ name, previous: listed.has(name) ? "allowed" : "absent" }));
|
|
169
|
+
if (changes.every((change) => change.previous === "allowed")) {
|
|
170
|
+
return { status: "unchanged", target: "onlyBuiltDependencies", changes };
|
|
171
|
+
}
|
|
172
|
+
const next = [...lines];
|
|
173
|
+
const appended = changes.filter((change) => change.previous === "absent").map((change) => `${indent}- ${yamlPackageKey(change.name)}`);
|
|
174
|
+
next.splice(insertionPoint(block), 0, ...appended);
|
|
175
|
+
return { status: "updated", target: "onlyBuiltDependencies", changes, text: next.join(eol) };
|
|
176
|
+
}
|
|
177
|
+
function checkDocumentShape(lines) {
|
|
178
|
+
const content = lines.filter((line) => line.trim() !== "" && !/^\s*#/.test(line));
|
|
179
|
+
if (content.some((line) => /^(---|\.\.\.)(\s|$)/.test(line)))
|
|
180
|
+
return "the file has YAML document markers (--- / ...)";
|
|
181
|
+
const first = content[0];
|
|
182
|
+
if (first !== void 0 && !/^(?:'[^']*'|"[^"]*"|[^\s#'"?{[\]-][^:]*?)\s*:(?:\s|$)/.test(first)) {
|
|
183
|
+
return "the file is not a plain mapping with its keys at column 0";
|
|
184
|
+
}
|
|
185
|
+
return void 0;
|
|
186
|
+
}
|
|
187
|
+
function expandFlow(lines, key) {
|
|
188
|
+
const keyLine = topLevelKeyLine(key);
|
|
189
|
+
const index = lines.findIndex((line) => keyLine.test(line));
|
|
190
|
+
if (index === -1)
|
|
191
|
+
return lines;
|
|
192
|
+
const match = /^(.*?:)\s*([{[])(.*)([}\]])\s*(#.*)?$/.exec(lines[index]);
|
|
193
|
+
if (!match)
|
|
194
|
+
return lines;
|
|
195
|
+
const [, head, open, inner, close, comment] = match;
|
|
196
|
+
const isMap = key === "allowBuilds";
|
|
197
|
+
if (isMap && (open !== "{" || close !== "}") || !isMap && (open !== "[" || close !== "]")) {
|
|
198
|
+
return `${key} is a flow collection of the wrong kind`;
|
|
199
|
+
}
|
|
200
|
+
const items = inner.trim() === "" ? [] : inner.split(",").map((item) => item.trim());
|
|
201
|
+
const valid = isMap ? (item) => /^(?:'[^']*'|"[^"]*"|[^\s'"#-]\S*?):\s+[^{}[\],]+$/.test(item) : (item) => /^(?:'[^']*'|"[^"]*"|[^\s'"#{}[\],:-][^{}[\],:]*)$/.test(item);
|
|
202
|
+
if (items.some((item) => !valid(item)))
|
|
203
|
+
return `${key} is a flow collection this parser does not split`;
|
|
204
|
+
const block = items.map((item) => isMap ? ` ${item}` : ` - ${item}`);
|
|
205
|
+
return [...lines.slice(0, index), `${head}${comment ? ` ${comment}` : ""}`, ...block, ...lines.slice(index + 1)];
|
|
206
|
+
}
|
|
207
|
+
function topLevelKeyLine(key) {
|
|
208
|
+
return new RegExp(`^(['"]?)${key}\\1\\s*:(.*)$`);
|
|
209
|
+
}
|
|
210
|
+
function findBlock(lines, key) {
|
|
211
|
+
const keyLine = topLevelKeyLine(key);
|
|
212
|
+
const keyIndices = lines.flatMap((line, index) => keyLine.test(line) ? [index] : []);
|
|
213
|
+
const mentions = lines.filter((line) => !/^\s*#/.test(line) && new RegExp(`\\b${key}\\b`).test(line));
|
|
214
|
+
if (mentions.length > keyIndices.length)
|
|
215
|
+
return `${key} appears somewhere other than a top-level key`;
|
|
216
|
+
if (keyIndices.length === 0)
|
|
217
|
+
return void 0;
|
|
218
|
+
if (keyIndices.length > 1)
|
|
219
|
+
return `${key} appears more than once at the top level`;
|
|
220
|
+
const keyIndex = keyIndices[0];
|
|
221
|
+
const rest = (keyLine.exec(lines[keyIndex])?.[2] ?? "").replace(/\s+#.*$/, "").trim();
|
|
222
|
+
if (rest !== "")
|
|
223
|
+
return `${key} is not a block (found "${key}: ${rest}")`;
|
|
224
|
+
const entries = [];
|
|
225
|
+
for (let index = keyIndex + 1; index < lines.length; index++) {
|
|
226
|
+
const line = lines[index];
|
|
227
|
+
if (line.trim() === "" || /^\s*#/.test(line))
|
|
228
|
+
continue;
|
|
229
|
+
if (/^\S/.test(line)) {
|
|
230
|
+
if (line.startsWith("-"))
|
|
231
|
+
return `${key} uses a column-0 list`;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
entries.push(index);
|
|
235
|
+
}
|
|
236
|
+
if (entries.length > 0) {
|
|
237
|
+
const indent = leadingSpace(lines[entries[0]]);
|
|
238
|
+
if (entries.some((index) => leadingSpace(lines[index]) !== indent))
|
|
239
|
+
return `${key} has entries at more than one indentation`;
|
|
240
|
+
}
|
|
241
|
+
return { keyIndex, entries };
|
|
242
|
+
}
|
|
243
|
+
function insertionPoint(block) {
|
|
244
|
+
return (block.entries.length > 0 ? block.entries[block.entries.length - 1] : block.keyIndex) + 1;
|
|
245
|
+
}
|
|
246
|
+
function splitValue(raw) {
|
|
247
|
+
const match = /^('[^']*'|"[^"]*"|[^#]*?)\s*(#.*)?$/.exec(raw.trim());
|
|
248
|
+
return { value: match?.[1] ?? raw.trim(), comment: match?.[2] ?? "" };
|
|
249
|
+
}
|
|
250
|
+
function classify(value) {
|
|
251
|
+
if (/^(true|True|TRUE)$/.test(value))
|
|
252
|
+
return "allowed";
|
|
253
|
+
if (/^(false|False|FALSE)$/.test(value))
|
|
254
|
+
return "denied";
|
|
255
|
+
const unquoted = /^(['"])(.*)\1$/.exec(value)?.[2] ?? value;
|
|
256
|
+
if (unquoted === PENDING_PLACEHOLDER)
|
|
257
|
+
return "pending";
|
|
258
|
+
return "other";
|
|
259
|
+
}
|
|
260
|
+
function yamlPackageKey(name) {
|
|
261
|
+
return /^[a-z][a-z0-9._~-]*$/i.test(name) && !/^(true|false|null|yes|no|on|off|y|n)$/i.test(name) ? name : `'${name}'`;
|
|
262
|
+
}
|
|
263
|
+
function leadingSpace(line) {
|
|
264
|
+
return /^(\s*)/.exec(line)?.[1] ?? "";
|
|
265
|
+
}
|
|
266
|
+
function escapeRegExp(text) {
|
|
267
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
268
|
+
}
|
|
269
|
+
function missingFromLockfile(lockfileText, names) {
|
|
270
|
+
const installed = [];
|
|
271
|
+
let section = "";
|
|
272
|
+
for (const line of lockfileText.split(/\r?\n/)) {
|
|
273
|
+
if (/^\S/.test(line))
|
|
274
|
+
section = /^([A-Za-z]+):/.exec(line)?.[1] ?? "";
|
|
275
|
+
else if (section === "packages" || section === "snapshots")
|
|
276
|
+
installed.push(line);
|
|
277
|
+
}
|
|
278
|
+
const text = installed.join("\n");
|
|
279
|
+
return names.filter((name) => !new RegExp(`^ {2}(['"]?)/?${escapeRegExp(name)}@`, "m").test(text));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/generators/allow-builds/render.ts
|
|
283
|
+
var ALLOW_BUILDS_LABEL = {
|
|
284
|
+
allowed: "already true",
|
|
285
|
+
denied: "false \u2192 true",
|
|
286
|
+
pending: "awaiting a decision \u2192 true",
|
|
287
|
+
other: "another value \u2192 true",
|
|
288
|
+
absent: "added (true)"
|
|
289
|
+
};
|
|
290
|
+
function renderAllowBuildsReport(report) {
|
|
291
|
+
const rebuild = `pnpm rebuild ${report.packages.join(" ")}`;
|
|
292
|
+
if (report.refused === void 0) {
|
|
293
|
+
const lines = [
|
|
294
|
+
`allow-builds \u2014 ${report.applied ? "applied" : "preview (nothing written)"}`,
|
|
295
|
+
"",
|
|
296
|
+
`pnpm-workspace.yaml \u203A ${report.target}`,
|
|
297
|
+
...report.changes.map((change) => ` ${change.name}: ${label(report.target, change.previous)}`),
|
|
298
|
+
""
|
|
299
|
+
];
|
|
300
|
+
if (report.namesUnchecked) lines.push("The names were not checked: there is no pnpm-lock.yaml.", "");
|
|
301
|
+
if (report.applied) {
|
|
302
|
+
lines.push(`Running: ${rebuild}`);
|
|
303
|
+
} else {
|
|
304
|
+
lines.push(
|
|
305
|
+
`Then: ${rebuild}`,
|
|
306
|
+
"Allowing a build lets that package's install script run with your user's permissions.",
|
|
307
|
+
"",
|
|
308
|
+
`To apply: pnpm nx g @clearlydev/nx-archweave:allow-builds --packages=${report.packages.join(",")} --apply`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
return lines.join("\n");
|
|
312
|
+
}
|
|
313
|
+
const head = "allow-builds \u2014 refused (nothing written)";
|
|
314
|
+
switch (report.refused) {
|
|
315
|
+
case "npm":
|
|
316
|
+
return [
|
|
317
|
+
head,
|
|
318
|
+
"This is an npm workspace: allow-builds edits pnpm settings only. npm runs dependency install scripts",
|
|
319
|
+
"itself unless ignore-scripts is set, so there is nothing to re-allow; npm 11 records an approval with",
|
|
320
|
+
` npm install-scripts approve ${report.packages.join(" ")}`
|
|
321
|
+
].join("\n");
|
|
322
|
+
case "yarn":
|
|
323
|
+
return [
|
|
324
|
+
head,
|
|
325
|
+
"This is a yarn workspace: allow-builds edits pnpm settings only. yarn classic runs dependency",
|
|
326
|
+
"install scripts itself; yarn berry runs them unless .yarnrc.yml sets enableScripts: false \u2014",
|
|
327
|
+
"remove that line (or set it to true) and run `yarn install` to let them run."
|
|
328
|
+
].join("\n");
|
|
329
|
+
case "bun":
|
|
330
|
+
return [
|
|
331
|
+
head,
|
|
332
|
+
"This is a bun workspace: allow-builds edits pnpm settings only. bun blocks the install scripts of",
|
|
333
|
+
"dependencies outside its trusted list; `bun pm untrusted` lists the blocked ones, and",
|
|
334
|
+
` bun pm trust ${report.packages.join(" ")}`,
|
|
335
|
+
"runs their scripts and records them in package.json trustedDependencies."
|
|
336
|
+
].join("\n");
|
|
337
|
+
case "no-package-manager":
|
|
338
|
+
return [head, "There is no pnpm-lock.yaml or pnpm-workspace.yaml here \u2014 allow-builds edits pnpm settings only."].join("\n");
|
|
339
|
+
case "no-workspace-yaml":
|
|
340
|
+
return [
|
|
341
|
+
head,
|
|
342
|
+
"This pnpm project has no pnpm-workspace.yaml, where pnpm keeps its build decisions. Add one with",
|
|
343
|
+
" allowBuilds:",
|
|
344
|
+
...report.packages.map((name) => ` ${yamlPackageKey(name)}: true`),
|
|
345
|
+
`then run "${rebuild}".`
|
|
346
|
+
].join("\n");
|
|
347
|
+
case "unknown-packages":
|
|
348
|
+
return [
|
|
349
|
+
head,
|
|
350
|
+
`These packages are not in pnpm-lock.yaml: ${report.unknown.join(", ")} \u2014 check the names; only an installed package has a build to run.`
|
|
351
|
+
].join("\n");
|
|
352
|
+
case "unrecognized":
|
|
353
|
+
return [
|
|
354
|
+
head,
|
|
355
|
+
`pnpm-workspace.yaml is in a shape allow-builds does not edit: ${report.reason}.`,
|
|
356
|
+
`Instead, allow ${report.packages.join(", ")} in pnpm-workspace.yaml yourself \u2014 "<package>: true" under allowBuilds`,
|
|
357
|
+
`(pnpm 11; pnpm 10 from 10.28), or a "- <package>" item under onlyBuiltDependencies on older pnpm 10 \u2014 then run "${rebuild}".`
|
|
358
|
+
].join("\n");
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function label(target, previous) {
|
|
362
|
+
if (target === "allowBuilds") return ALLOW_BUILDS_LABEL[previous];
|
|
363
|
+
return previous === "allowed" ? "already listed" : "added to the list";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/generators/allow-builds/generator.ts
|
|
367
|
+
var WORKSPACE_YAML = "pnpm-workspace.yaml";
|
|
368
|
+
var LOCKFILE = "pnpm-lock.yaml";
|
|
369
|
+
var MODULES_MANIFEST = "node_modules/.modules.yaml";
|
|
370
|
+
var spawnPnpm = (args, cwd) => {
|
|
371
|
+
const spawnable = spawnableCommand("pnpm", args);
|
|
372
|
+
const result = spawnSync(spawnable.command, spawnable.args, {
|
|
373
|
+
cwd,
|
|
374
|
+
stdio: "inherit",
|
|
375
|
+
windowsVerbatimArguments: spawnable.windowsVerbatimArguments
|
|
376
|
+
});
|
|
377
|
+
return { status: result.status, error: result.error };
|
|
378
|
+
};
|
|
379
|
+
function runAllowBuilds(tree, options, deps = {}) {
|
|
380
|
+
const packages = [...new Set(options.packages.map((name) => name.trim()))];
|
|
381
|
+
const invalid = packages.filter((name) => !isPlainPackageName(name));
|
|
382
|
+
if (packages.length === 0 || invalid.length > 0) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
`Expected npm package names (e.g. --packages=sharp,@swc/core)${invalid.length > 0 ? `; not a package name: ${invalid.map((name) => JSON.stringify(name)).join(", ")}` : ""}.`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
const apply = (options.apply ?? false) && process.env["NX_DRY_RUN"] !== "true";
|
|
388
|
+
const pm = detectWorkspacePackageManager((path) => tree.exists(path), (path) => tree.read(path, "utf-8"));
|
|
389
|
+
if (pm === "npm") return { report: { packages, refused: "npm" } };
|
|
390
|
+
if (pm === "yarn") return { report: { packages, refused: "yarn" } };
|
|
391
|
+
if (pm === "bun") return { report: { packages, refused: "bun" } };
|
|
392
|
+
if (pm === null) return { report: { packages, refused: "no-package-manager" } };
|
|
393
|
+
const yaml = tree.read(WORKSPACE_YAML, "utf-8");
|
|
394
|
+
if (yaml === null) return { report: { packages, refused: "no-workspace-yaml" } };
|
|
395
|
+
const lockfile = tree.read(LOCKFILE, "utf-8");
|
|
396
|
+
const unknown = lockfile === null ? [] : missingFromLockfile(lockfile, packages);
|
|
397
|
+
if (unknown.length > 0) return { report: { packages, refused: "unknown-packages", unknown } };
|
|
398
|
+
const modules = tree.read(MODULES_MANIFEST, "utf-8");
|
|
399
|
+
const pnpmMajor = modules === null ? void 0 : pnpmMajorFromModulesManifest(modules);
|
|
400
|
+
const result = allowPackageBuilds(yaml, packages, { pnpmMajor });
|
|
401
|
+
if (result.status === "unrecognized") return { report: { packages, refused: "unrecognized", reason: result.reason } };
|
|
402
|
+
const report = {
|
|
403
|
+
packages,
|
|
404
|
+
target: result.target,
|
|
405
|
+
changes: result.changes,
|
|
406
|
+
applied: apply,
|
|
407
|
+
...lockfile === null ? { namesUnchecked: true } : {}
|
|
408
|
+
};
|
|
409
|
+
if (!apply) return { report };
|
|
410
|
+
if (result.status === "updated") tree.write(WORKSPACE_YAML, result.text);
|
|
411
|
+
const exec = deps.exec ?? spawnPnpm;
|
|
412
|
+
const root = tree.root;
|
|
413
|
+
const callback = () => {
|
|
414
|
+
const args = ["rebuild", ...packages];
|
|
415
|
+
const command = `pnpm ${args.join(" ")}`;
|
|
416
|
+
const outcome = exec(args, root);
|
|
417
|
+
if (outcome.error) {
|
|
418
|
+
throw new Error(`"${command}" could not start: ${outcome.error.message}. ${WORKSPACE_YAML} already allows the build \u2014 run "${command}" yourself.`);
|
|
419
|
+
}
|
|
420
|
+
if (outcome.status !== 0) {
|
|
421
|
+
throw new Error(`"${command}" exited with ${outcome.status ?? "a signal"}. ${WORKSPACE_YAML} already allows the build \u2014 fix the cause above, then run "${command}" yourself.`);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
return { report, callback };
|
|
425
|
+
}
|
|
426
|
+
async function allowBuildsGenerator(tree, options, deps = {}) {
|
|
427
|
+
const { report, callback } = runAllowBuilds(tree, options, deps);
|
|
428
|
+
console.log(renderAllowBuildsReport(report));
|
|
429
|
+
return callback;
|
|
430
|
+
}
|
|
431
|
+
var generator_default = allowBuildsGenerator;
|
|
432
|
+
export {
|
|
433
|
+
allowBuildsGenerator,
|
|
434
|
+
generator_default as default,
|
|
435
|
+
runAllowBuilds
|
|
436
|
+
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { yamlPackageKey } from '@nx-archweave/template-engine';
|
|
2
|
+
const ALLOW_BUILDS_LABEL = {
|
|
3
|
+
allowed: 'already true',
|
|
4
|
+
denied: 'false → true',
|
|
5
|
+
pending: 'awaiting a decision → true',
|
|
6
|
+
other: 'another value → true',
|
|
7
|
+
absent: 'added (true)',
|
|
8
|
+
};
|
|
9
|
+
export function renderAllowBuildsReport(report) {
|
|
10
|
+
const rebuild = `pnpm rebuild ${report.packages.join(' ')}`;
|
|
11
|
+
if (report.refused === undefined) {
|
|
12
|
+
const lines = [
|
|
13
|
+
`allow-builds — ${report.applied ? 'applied' : 'preview (nothing written)'}`,
|
|
14
|
+
'',
|
|
15
|
+
`pnpm-workspace.yaml › ${report.target}`,
|
|
16
|
+
...report.changes.map((change) => ` ${change.name}: ${label(report.target, change.previous)}`),
|
|
17
|
+
'',
|
|
18
|
+
];
|
|
19
|
+
if (report.namesUnchecked)
|
|
20
|
+
lines.push('The names were not checked: there is no pnpm-lock.yaml.', '');
|
|
21
|
+
if (report.applied) {
|
|
22
|
+
lines.push(`Running: ${rebuild}`);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
lines.push(`Then: ${rebuild}`, "Allowing a build lets that package's install script run with your user's permissions.", '', `To apply: pnpm nx g @clearlydev/nx-archweave:allow-builds --packages=${report.packages.join(',')} --apply`);
|
|
26
|
+
}
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
}
|
|
29
|
+
const head = 'allow-builds — refused (nothing written)';
|
|
30
|
+
switch (report.refused) {
|
|
31
|
+
case 'npm':
|
|
32
|
+
return [
|
|
33
|
+
head,
|
|
34
|
+
'This is an npm workspace: allow-builds edits pnpm settings only. npm runs dependency install scripts',
|
|
35
|
+
'itself unless ignore-scripts is set, so there is nothing to re-allow; npm 11 records an approval with',
|
|
36
|
+
` npm install-scripts approve ${report.packages.join(' ')}`,
|
|
37
|
+
].join('\n');
|
|
38
|
+
case 'yarn':
|
|
39
|
+
return [
|
|
40
|
+
head,
|
|
41
|
+
'This is a yarn workspace: allow-builds edits pnpm settings only. yarn classic runs dependency',
|
|
42
|
+
'install scripts itself; yarn berry runs them unless .yarnrc.yml sets enableScripts: false —',
|
|
43
|
+
'remove that line (or set it to true) and run `yarn install` to let them run.',
|
|
44
|
+
].join('\n');
|
|
45
|
+
case 'bun':
|
|
46
|
+
return [
|
|
47
|
+
head,
|
|
48
|
+
'This is a bun workspace: allow-builds edits pnpm settings only. bun blocks the install scripts of',
|
|
49
|
+
'dependencies outside its trusted list; `bun pm untrusted` lists the blocked ones, and',
|
|
50
|
+
` bun pm trust ${report.packages.join(' ')}`,
|
|
51
|
+
'runs their scripts and records them in package.json trustedDependencies.',
|
|
52
|
+
].join('\n');
|
|
53
|
+
case 'no-package-manager':
|
|
54
|
+
return [head, 'There is no pnpm-lock.yaml or pnpm-workspace.yaml here — allow-builds edits pnpm settings only.'].join('\n');
|
|
55
|
+
case 'no-workspace-yaml':
|
|
56
|
+
return [
|
|
57
|
+
head,
|
|
58
|
+
'This pnpm project has no pnpm-workspace.yaml, where pnpm keeps its build decisions. Add one with',
|
|
59
|
+
' allowBuilds:',
|
|
60
|
+
...report.packages.map((name) => ` ${yamlPackageKey(name)}: true`),
|
|
61
|
+
`then run "${rebuild}".`,
|
|
62
|
+
].join('\n');
|
|
63
|
+
case 'unknown-packages':
|
|
64
|
+
return [
|
|
65
|
+
head,
|
|
66
|
+
`These packages are not in pnpm-lock.yaml: ${report.unknown.join(', ')} — check the names; only an installed package has a build to run.`,
|
|
67
|
+
].join('\n');
|
|
68
|
+
case 'unrecognized':
|
|
69
|
+
return [
|
|
70
|
+
head,
|
|
71
|
+
`pnpm-workspace.yaml is in a shape allow-builds does not edit: ${report.reason}.`,
|
|
72
|
+
`Instead, allow ${report.packages.join(', ')} in pnpm-workspace.yaml yourself — "<package>: true" under allowBuilds`,
|
|
73
|
+
`(pnpm 11; pnpm 10 from 10.28), or a "- <package>" item under onlyBuiltDependencies on older pnpm 10 — then run "${rebuild}".`,
|
|
74
|
+
].join('\n');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function label(target, previous) {
|
|
78
|
+
if (target === 'allowBuilds')
|
|
79
|
+
return ALLOW_BUILDS_LABEL[previous];
|
|
80
|
+
// a list item is either there or not — denied / pending / other only exist in allowBuilds
|
|
81
|
+
return previous === 'allowed' ? 'already listed' : 'added to the list';
|
|
82
|
+
}
|