block-in-file 1.0.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.
Files changed (67) hide show
  1. package/- +3 -0
  2. package/.beads/README.md +85 -0
  3. package/.beads/config.yaml +67 -0
  4. package/.beads/interactions.jsonl +0 -0
  5. package/.beads/issues.jsonl +23 -0
  6. package/.beads/metadata.json +4 -0
  7. package/.git-blame-ignore-revs +2 -0
  8. package/.gitattributes +3 -0
  9. package/.prettierrc.json +5 -0
  10. package/AGENTS.md +40 -0
  11. package/README.md +122 -0
  12. package/block-in-file.ts +150 -0
  13. package/content +10 -0
  14. package/deno.json +14 -0
  15. package/deno.lock +1084 -0
  16. package/doc/PLAN-envsubst.md +200 -0
  17. package/doc/PLAN-restructure.md +114 -0
  18. package/package.json +44 -0
  19. package/src/attributes.ts +161 -0
  20. package/src/backup.ts +180 -0
  21. package/src/block-parser.ts +170 -0
  22. package/src/block-remover.ts +128 -0
  23. package/src/conflict-detection.ts +179 -0
  24. package/src/defaults.ts +23 -0
  25. package/src/envsubst.ts +59 -0
  26. package/src/file-processor.ts +378 -0
  27. package/src/index.ts +5 -0
  28. package/src/input.ts +69 -0
  29. package/src/mode-handler.ts +39 -0
  30. package/src/output.ts +107 -0
  31. package/src/plugins/.beads/.local_version +1 -0
  32. package/src/plugins/.beads/issues.jsonl +21 -0
  33. package/src/plugins/.beads/metadata.json +4 -0
  34. package/src/plugins/config.ts +282 -0
  35. package/src/plugins/diff.ts +109 -0
  36. package/src/plugins/io.ts +72 -0
  37. package/src/plugins/logger.ts +41 -0
  38. package/src/tags/tag-merger.ts +31 -0
  39. package/src/tags/tag-mode.ts +1 -0
  40. package/src/tags/tag.ts +36 -0
  41. package/src/tags/tags.ts +4 -0
  42. package/src/tags/types.ts +4 -0
  43. package/src/timestamp.ts +39 -0
  44. package/src/types.ts +32 -0
  45. package/src/validation.ts +11 -0
  46. package/test/additive-cli.test.ts +109 -0
  47. package/test/additive.test.ts +233 -0
  48. package/test/attributes-integration.test.ts +161 -0
  49. package/test/attributes.test.ts +100 -0
  50. package/test/backup.test.ts +386 -0
  51. package/test/block-in-file.test.ts +235 -0
  52. package/test/block-parser.test.ts +221 -0
  53. package/test/block-remover.test.ts +209 -0
  54. package/test/cli.test.ts +254 -0
  55. package/test/defaults.test.ts +38 -0
  56. package/test/envsubst-edge-cases.test.ts +116 -0
  57. package/test/envsubst-integration.test.ts +78 -0
  58. package/test/envsubst.test.ts +184 -0
  59. package/test/input.test.ts +86 -0
  60. package/test/mode.test.ts +193 -0
  61. package/test/output.test.ts +44 -0
  62. package/test/tag-merger.test.ts +176 -0
  63. package/test/tags.test.ts +116 -0
  64. package/test/timestamp-integration.test.ts +209 -0
  65. package/test/timestamp.test.ts +76 -0
  66. package/tsconfig.json +16 -0
  67. package/vitest.config.ts +8 -0
@@ -0,0 +1,150 @@
1
+ #!/usr/bin/env -S npx tsx
2
+ import { cli, define } from "gunshi";
3
+ import { pluginId as configId, type ConfigExtension } from "./src/plugins/config.ts";
4
+ import { pluginId as loggerId, type LoggerExtension } from "./src/plugins/logger.ts";
5
+ import { pluginId as ioId, type IOExtension } from "./src/plugins/io.ts";
6
+ import { pluginId as diffId, type DiffExtension } from "./src/plugins/diff.ts";
7
+ import logger from "./src/plugins/logger.ts";
8
+ import config from "./src/plugins/config.ts";
9
+ import io from "./src/plugins/io.ts";
10
+ import diff from "./src/plugins/diff.ts";
11
+ import { processFile, type ProcessContext, type ProcessResult } from "./src/file-processor.ts";
12
+
13
+ const command = define<{
14
+ extensions: Record<typeof configId, ConfigExtension> &
15
+ Record<typeof loggerId, LoggerExtension> &
16
+ Record<typeof ioId, IOExtension> &
17
+ Record<typeof diffId, DiffExtension>;
18
+ }>({
19
+ name: "insert",
20
+ description: "Insert & update blocks of text in file",
21
+ run: async (ctx) => {
22
+ const { extensions, positionals } = ctx;
23
+ const configExt = extensions[configId];
24
+ const logger = extensions[loggerId];
25
+ const io = extensions[ioId];
26
+ const diffExt = extensions[diffId];
27
+
28
+ const files = (positionals as string[]) || [];
29
+
30
+ if (files.length === 0 && !configExt.diff) {
31
+ if (configExt.output === "---" || !configExt.output) {
32
+ throw new Error("Need file argument or output target");
33
+ }
34
+ }
35
+
36
+ if (files.length === 0 && configExt.diff) {
37
+ throw new Error("Need file argument for diff mode");
38
+ }
39
+
40
+ if (configExt.debug) {
41
+ logger.debug(`Processing ${files.length} file(s)`);
42
+ }
43
+
44
+ const opener = `${configExt.comment} ${configExt.name} ${configExt.markerStart}`;
45
+ const closer = `${configExt.comment} ${configExt.name} ${configExt.markerEnd}`;
46
+
47
+ const inputBlock = await io.readFile(configExt.input);
48
+ if (configExt.debug) {
49
+ logger.debug(`Input block: ${inputBlock.slice(0, 50)}...`);
50
+ }
51
+
52
+ const before = configExt.before ? new RegExp(configExt.before) : undefined;
53
+ const after = configExt.after ? new RegExp(configExt.after) : undefined;
54
+
55
+ const results: ProcessResult[] = [];
56
+
57
+ for (const file of files) {
58
+ const fileExists = await io.fileExists(file);
59
+ let fileContent = "";
60
+
61
+ if (fileExists) {
62
+ try {
63
+ fileContent = await io.readFile(file);
64
+ } catch {
65
+ if (configExt.debug) {
66
+ logger.debug(`File does not exist yet: ${file}`);
67
+ }
68
+ }
69
+ }
70
+
71
+ const processContext: ProcessContext = {
72
+ file,
73
+ fileExists,
74
+ fileContent,
75
+ inputBlock,
76
+ opener,
77
+ closer,
78
+ before,
79
+ after,
80
+ mode: configExt.mode,
81
+ force: configExt.force,
82
+ create: configExt.create === true || configExt.create === "file",
83
+ validateCmd: configExt.validate,
84
+ debug: configExt.debug,
85
+ logger,
86
+ io,
87
+ diffExt,
88
+ output: configExt.output,
89
+ dos: configExt.dos,
90
+ backupOptions: configExt.backupOptions,
91
+ tempExt: configExt.tempExt,
92
+ tempExtAtomic: configExt.tempExtAtomic,
93
+ tempExtPrevalidate: configExt.tempExtPrevalidate,
94
+ appendNewline: configExt.appendNewline,
95
+ attributes: configExt.attributes,
96
+ removeAll: configExt.removeAll,
97
+ removeOrphans: configExt.removeOrphans,
98
+ envsubst: configExt.envsubst,
99
+ additive: configExt.additive,
100
+ additiveBefore: configExt.additiveBefore,
101
+ additiveAfter: configExt.additiveAfter,
102
+ timestamp: configExt.timestamp,
103
+ tagMode: configExt.tagMode,
104
+ };
105
+
106
+ const result = await processFile(processContext);
107
+ results.push(result);
108
+
109
+ if (configExt.diff && result.status === "written" && result.originalContent !== undefined) {
110
+ await diffExt.writeDiff(
111
+ configExt.diff,
112
+ result.originalContent,
113
+ result.outputs?.join("\n") || "",
114
+ file,
115
+ );
116
+ }
117
+ }
118
+
119
+ if (configExt.debug || configExt.removeAll) {
120
+ const written = results.filter((r) => r.status === "written").length;
121
+ const skipped = results.filter((r) => r.status === "skipped").length;
122
+ const removed = results.filter((r) => r.status === "removed").length;
123
+ const totalRemoved = results
124
+ .filter((r) => r.status === "removed")
125
+ .reduce((sum, r) => sum + (r.removalStats?.removed || 0), 0);
126
+ const totalOrphans = results
127
+ .filter((r) => r.status === "removed")
128
+ .reduce((sum, r) => sum + (r.removalStats?.orphans || 0), 0);
129
+
130
+ if (configExt.removeAll) {
131
+ logger.log(
132
+ `Done! Removed: ${removed} files, ${totalRemoved} blocks, ${totalOrphans} orphans`,
133
+ );
134
+ } else {
135
+ logger.log(`Done! Written: ${written}, Skipped: ${skipped}`);
136
+ }
137
+ } else {
138
+ logger.log("Done!");
139
+ }
140
+ },
141
+ });
142
+
143
+ cli(process.argv.slice(2), command, {
144
+ name: "block-in-file",
145
+ version: "1.0.0",
146
+ plugins: [logger(), config(), io(), diff()],
147
+ }).catch((err) => {
148
+ console.error(err);
149
+ process.exit(1);
150
+ });
package/content ADDED
@@ -0,0 +1,10 @@
1
+ # testblock start [timestamp:1770765407456000000]
2
+
3
+ # testblock end
4
+
5
+ // testblock start [timestamp:1770765407270000000]
6
+
7
+ // testblock end
8
+ # testblock BEGIN [timestamp:1770765407]
9
+
10
+ # testblock END
package/deno.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "tasks": {
3
+ "block-in-file": "deno run --allow-read --allow-write --allow-net block-in-file.ts",
4
+ "install:error-perms": "deno install -f --allow-read --allow-write --allow-net block-in-file.ts",
5
+ "install:dir": "deno install -f --allow-read --allow-write --allow-net --root $ROOT --global block-in-file.ts"
6
+ },
7
+ "fmt": {
8
+ "options": {
9
+ "lineWidth": 160,
10
+ "semiColons": false,
11
+ "useTabs": true
12
+ }
13
+ }
14
+ }